<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>sadig.dev — Writing</title>
    <link>https://www.sadig.dev</link>
    <description>Long-form notes from Sadig Muradov.</description>
    <language>en-us</language>
    <atom:link href="https://www.sadig.dev/rss.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Tue, 05 May 2026 23:46:57 GMT</lastBuildDate>
    
    <item>
      <title>Simplifying Data Tracking — Queryable Prefixed Django ID Fields</title>
      <link>https://www.sadig.dev/posts/prefixed-django-ids</link>
      <guid isPermaLink="true">https://www.sadig.dev/posts/prefixed-django-ids</guid>
      <pubDate>Tue, 27 Feb 2024 00:00:00 GMT</pubDate>
      <description>Auto-incremented, prefixed, human-readable IDs (INV00001, ORD0003) in Django — four ways, with the trade-offs of each.</description>
      <category>django</category><category>postgres</category>
      <content:encoded><![CDATA[<p>At some point, when you develop an application, you may need a unique human-readable identifier for some of the models. This article explores different methods of creating auto-incremented and prefixed IDs for Django models, such as <code>INV00001</code> or <code>ORD0003</code>, providing a detailed guide with examples.</p>
<h2 id="understanding-prefixed-auto-incremented-ids-in-django"><a href="#understanding-prefixed-auto-incremented-ids-in-django">Understanding Prefixed Auto-Incremented IDs in Django</a></h2>
<p>Each model is typically assigned with an auto-incremented unique ID as the primary key. Whenever you define a model in Django, it will add this ID column/field to the model built-in if you don’t tell otherwise. Django and database thill increments with each new record automatically manage this ID. The <code>AutoField</code> in Django is a built-in field type that handles this behavior. For example:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Order</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">    id</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.AutoField(</span><span style="color:#E36209;--shiki-dark:#F69D50">primary_key</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">True</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span></code></pre>
<p>However, developers often require more than just a numerical ID. We might need a prefixed ID for better identification and sorting of records. For instance, adding a prefix such as <code>‘ORD’</code> to an order ID can make it more informative and easier to recognize.</p>
<hr>
<h2 id="benefits-of-using-prefixed-ids"><a href="#benefits-of-using-prefixed-ids">Benefits of Using Prefixed IDs</a></h2>
<p>Prefixed IDs in Django models offer a multitude of advantages over traditional numeric auto-incremented identifiers</p>
<h3 id="1-readability"><a href="#1-readability">#1 Readability</a></h3>
<p>One significant benefit is the enhanced readability and context they provide. By including a prefix, IDs can immediately convey information about the type of object they represent, making them more intuitive for developers and users alike. For instance, an order with the ID <code>ORD-1001</code> is easily identifiable as an order, unlike a nondescript number like <code>1001</code>. This makes communicating the issues with users during the support ticket handling much more straightforward.</p>
<h3 id="2-data-organization"><a href="#2-data-organization">#2 Data Organization</a></h3>
<p>Another key advantage is the improved organization and sorting of records. Prefixed IDs can help categorize data, particularly useful in systems with multiple entity types. This categorization can simplify data retrieval and manipulation and enhance the overall database management experience.</p>
<h3 id="3-better-security"><a href="#3-better-security">#3 Better Security</a></h3>
<p>Moreover, using prefixed IDs can contribute to better security practices. They can obscure a system’s actual number of records, making it less noticeable to infer the dataset’s size or the records’ creation sequence. This can be a subtle yet effective deterrent against specific data enumeration attacks.</p>
<hr>
<h2 id="exploring-different-methods-for-implementing-prefixed-id-fields"><a href="#exploring-different-methods-for-implementing-prefixed-id-fields">Exploring different methods for implementing prefixed ID fields</a></h2>
<p>Implementing human-readable, queryable, prefixed, and automatically incremented ID-like fields requires a bit of customization but dramatically enhances the usability and readability of your data. To follow along with the provided examples, you first must set up a Django project.</p>
<h3 id="setting-up-the-django-project"><a href="#setting-up-the-django-project">Setting up the Django project</a></h3>
<p>Setting up a Django project with PostgreSQL is a straightforward process. Here are the steps to follow:</p>
<ol>
<li>Install PostgreSQL and create a new database for your project. Or use Docker to setup a PostgreSQL database</li>
<li>Update the <code>DATABASES</code> setting in your Django project’s <code>settings.py</code> file to use PostgreSQL as the database backend.</li>
<li>Install the <code>psycopg2-binary</code> package, which is the PostgreSQL adapter for Python.</li>
<li>Run the Django migrations to create the necessary tables in the database.</li>
</ol>
<p>My Docker Compose setup for this tutorial:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># file: docker-compose.yml</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">version</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">"3"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># external services to connect to</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">services</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">  postgres</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    image</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">postgres:12</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    container_name</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">tutorial_postgres</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    restart</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">always</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    volumes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      - </span><span style="color:#032F62;--shiki-dark:#96D0FF">tutorial-postgres:/var/lib/postgresql/data</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    ports</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      - </span><span style="color:#032F62;--shiki-dark:#96D0FF">"5439:5432"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    env_file</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">.env</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    healthcheck</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      test</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: [</span><span style="color:#032F62;--shiki-dark:#96D0FF">"CMD-SHELL"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">]</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      interval</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">5s</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      timeout</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">5s</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      retries</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">volumes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">  tutorial-postgres</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span></code></pre>
<p>Django Settings:</p>
<pre><code># file: settings.py

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": os.environ.get("POSTGRES_DB"),
        "USER": os.environ.get("POSTGRES_USER"),
        "PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
        "HOST": os.environ.get("POSTGRES_HOST"),
        "PORT": os.environ.get("POSTGRES_PORT"),
    }
}
</code></pre>
<p>Requirements:</p>
<pre><code># python:  3.12.2
# file: requirements.txt
asgiref==3.7.2
Django==5.0.2
psycopg2-binary==2.9.9
sqlparse==0.4.4
</code></pre>
<p>Starting models:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.conf </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> settings</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># Create your models here.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Customer</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    user </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.OneToOneField(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        settings.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">AUTH_USER_MODEL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#F69D50">        on_delete</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">models.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CASCADE</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    )</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    address </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.TextField()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.user.username</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Order</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    customer </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.ForeignKey(Customer, </span><span style="color:#E36209;--shiki-dark:#F69D50">on_delete</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">models.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CASCADE</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    product_name </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_length</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">100</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    quantity </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.IntegerField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    price </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.product_name</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF"> - </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.quantity</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Invoice</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.OneToOneField(Order, </span><span style="color:#E36209;--shiki-dark:#F69D50">on_delete</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">models.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CASCADE</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    invoice_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    total_amount </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"Invoice for Order: </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.order.id</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span></code></pre>
<p>Now lets explore different method to implement prefixed ID.</p>
<hr>
<h2 id="method-1-overriding-save-method"><a href="#method-1-overriding-save-method">Method #1: Overriding <code>save</code> Method</a></h2>
<h3 id="concept"><a href="#concept">Concept</a></h3>
<p>The simplest way to start is by overriding the model’s <strong><code>save</code></strong> method. This method allows you to inject your logic for creating a custom ID before saving the model instance to the database.</p>
<h3 id="implementation"><a href="#implementation">Implementation</a></h3>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.utils.translation </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> gettext_lazy </span><span style="color:#D73A49;--shiki-dark:#F47067">as</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> _</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Order</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_length</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">unique</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">True</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">editable</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">False</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">    # Other fields</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> save</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self, </span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">args, </span><span style="color:#D73A49;--shiki-dark:#F47067">**</span><span style="color:#24292E;--shiki-dark:#ADBAC7">kwargs):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        if</span><span style="color:#D73A49;--shiki-dark:#F47067"> not</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.order_id:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            prefix </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'ORD-'</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            last_order </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Order.objects.all().values(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'order_id'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).order_by(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'id'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).last()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">            if</span><span style="color:#D73A49;--shiki-dark:#F47067"> not</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> last_order:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">                new_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">            else</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">                number </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> int</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(last_order.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"order_id"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">1</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).replace(prefix, </span><span style="color:#032F62;--shiki-dark:#96D0FF">''</span><span style="color:#24292E;--shiki-dark:#ADBAC7">) </span><span style="color:#D73A49;--shiki-dark:#F47067">or</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">                new_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> number </span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">            self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.order_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> prefix </span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> str</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(new_id).zfill(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">5</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">        super</span><span style="color:#24292E;--shiki-dark:#ADBAC7">().save(</span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">args, </span><span style="color:#D73A49;--shiki-dark:#F47067">**</span><span style="color:#24292E;--shiki-dark:#ADBAC7">kwargs)</span></span></code></pre>
<p><img src="/images/media/prefixed_id_fields/Screenshot_2024-02-27_at_02.06.52.png" alt="Screenshot 2024-02-27 at 02.06.52.png"></p>
<h3 id="explanation"><a href="#explanation">Explanation</a></h3>
<p>In this method, you check if the <code>order_id</code> is already set. If not, you generate a new one by finding the last order created, extracting its numeric part, incrementing it, and then concatenating it with the prefix and leading zeros to maintain the format.</p>
<h2 id="method-2-django-signals"><a href="#method-2-django-signals">Method #2: Django Signals</a></h2>
<h3 id="concept-1"><a href="#concept-1">Concept</a></h3>
<p>Django signals allow decoupling of applications by sending notifications when actions occur. A <strong><code>pre_save</code></strong> signal can be used to modify the instance before it’s saved without altering the model’s save method directly.</p>
<h3 id="implementation-1"><a href="#implementation-1">Implementation</a></h3>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># models.py</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db.models.signals </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> pre_save</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.dispatch </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> receiver</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Invoice</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    invoice_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_length</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">unique</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">True</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">editable</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">False</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">    # other fields</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#DCBDFB">@receiver</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(pre_save, </span><span style="color:#E36209;--shiki-dark:#F69D50">sender</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">Invoice)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> set_invoice_id</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(sender, instance, </span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">args, </span><span style="color:#D73A49;--shiki-dark:#F47067">**</span><span style="color:#24292E;--shiki-dark:#ADBAC7">kwargs):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    if</span><span style="color:#D73A49;--shiki-dark:#F47067"> not</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> instance.invoice_id:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        prefix </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'INV'</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        last_invoice </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Invoice.objects.all().order_by(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'id'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).last()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        if</span><span style="color:#D73A49;--shiki-dark:#F47067"> not</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> last_invoice:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            new_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        else</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            invoice_number </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> int</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(last_invoice.invoice_id.replace(prefix, </span><span style="color:#032F62;--shiki-dark:#96D0FF">''</span><span style="color:#24292E;--shiki-dark:#ADBAC7">))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            new_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> invoice_number </span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        instance.invoice_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> prefix </span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> str</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(new_id).zfill(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">4</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span></code></pre>
<p><img src="/images/media/prefixed_id_fields/Screenshot_2024-03-16_at_03.16.05.png" alt="Screenshot 2024-03-16 at 03.16.05.png"></p>
<h3 id="explanation-1"><a href="#explanation-1">Explanation</a></h3>
<p>This approach uses Django’s signal framework to listen for the <strong><code>pre_save</code></strong> event on the <strong><code>Invoice</code></strong> model. When an invoice is about to be saved and doesn’t have an <strong><code>invoice_id</code></strong> set, it calculates the new ID and sets it.</p>
<h2 id="method-3-custom-model-field-recommended"><a href="#method-3-custom-model-field-recommended">Method #3: Custom Model Field (Recommended)</a></h2>
<h3 id="concept-2"><a href="#concept-2">Concept</a></h3>
<p>Creating a custom model field allows you to encapsulate the logic for generating the custom ID, making your models cleaner and your custom ID logic reusable. The critical point in this method is “<strong>reusable</strong>.”</p>
<p><strong>Implementation</strong></p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> PrefixedIDField</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">CharField</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __init__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self, </span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">args, prefix</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF">'PRE'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, zfill</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">5</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#D73A49;--shiki-dark:#F47067">**</span><span style="color:#24292E;--shiki-dark:#ADBAC7">kwargs):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">        self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.prefix </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> prefix</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">        self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.zfill </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> zfill</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        kwargs[</span><span style="color:#032F62;--shiki-dark:#96D0FF">'max_length'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">] </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> kwargs.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'max_length'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)  </span><span style="color:#6A737D;--shiki-dark:#768390"># default max_length is 10</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">        super</span><span style="color:#24292E;--shiki-dark:#ADBAC7">().</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">__init__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">args, </span><span style="color:#D73A49;--shiki-dark:#F47067">**</span><span style="color:#24292E;--shiki-dark:#ADBAC7">kwargs)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> pre_save</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self, model_instance, add):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        if</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> add:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            last_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> model_instance.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">__class__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.objects.all().order_by(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'-id'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).first()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            lastest_value </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> getattr</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(last_id, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.attname, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">None</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">            if</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> last_id:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">                last_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> int</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(lastest_value.replace(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.prefix, </span><span style="color:#032F62;--shiki-dark:#96D0FF">''</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)) </span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">            else</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">                last_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            value </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">'</span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.prefix</span><span style="color:#005CC5;--shiki-dark:#F47067">}{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">str</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(last_id).zfill(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.zfill)</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">'</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">            setattr</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(model_instance, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.attname, value)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">            return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> value</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> super</span><span style="color:#24292E;--shiki-dark:#ADBAC7">().pre_save(model_instance, add)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Customer</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    customer_id </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> PrefixedIDField(</span><span style="color:#E36209;--shiki-dark:#F69D50">prefix</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF">'CUST'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">unique</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">True</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">editable</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">False</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">    # other fields...</span></span></code></pre>
<p><img src="/images/media/prefixed_id_fields/Screenshot_2024-03-16_at_03.33.02.png" alt="Screenshot 2024-03-16 at 03.33.02.png"></p>
<h3 id="explanation-2"><a href="#explanation-2">Explanation</a></h3>
<p>This custom field inherits from <strong><code>CharField</code></strong> and overrides the <strong><code>pre_save</code></strong> method to insert the logic for generating the custom ID. This method makes your models cleaner and the custom ID logic reusable across different models if needed.</p>
<h2 id="bonus-method-model-property"><a href="#bonus-method-model-property">Bonus Method: Model <code>@property</code></a></h2>
<h3 id="concept-3"><a href="#concept-3">Concept</a></h3>
<p>The <strong><code>@property</code></strong> decorator in Python allows you to define a method in your class that can be accessed like an attribute. This feature can be used in Django models to create a custom formatted ID that combines a prefix with the existing auto-incremented <strong><code>id</code></strong> field of a model instance. This method does not change the actual ID in the database but provides a formatted string that can be used in the user interface, reports, or exports.</p>
<h3 id="implementation-2"><a href="#implementation-2">Implementation</a></h3>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Customer</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">    # Other fields as necessary</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#DCBDFB">    @</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">property</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> prefixed_id</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        """Generates a human-readable ID with a prefix."""</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"USR-</span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.id</span><span style="color:#D73A49;--shiki-dark:#F47067">:05d</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span></code></pre>
<p>In this example, the <code>Customer</code> model still uses Django’s default auto-incrementing <strong><code>id</code></strong> as its primary key. The <strong><code>@property</code></strong> named <strong><code>prefixed_id</code></strong> generates a string that combines a ‘USR-’ prefix with the <strong><code>id</code></strong>, formatted as a five-digit number with leading zeros.</p>
<p><img src="/images/media/prefixed_id_fields/Screenshot_2024-03-16_at_03.52.21.png" alt="Screenshot 2024-03-16 at 03.52.21.png"></p>
<h3 id="explanation-3"><a href="#explanation-3">Explanation</a></h3>
<p>This method might seem advantages:</p>
<ul>
<li><strong>Non-intrusive</strong>: It doesn’t require any changes to the existing database schema or the Django model’s primary key mechanism. This means it can easily be added to existing models without requiring data migration or schema modification.</li>
<li><strong>Performance</strong>: Because the underlying <strong><code>id</code></strong> field is still an integer, database indexing and lookup performance are not affected. The custom format is applied only when accessing the <strong><code>prefixed_id</code></strong> property, typically at the application level.</li>
<li><strong>Flexibility</strong>: The formatting logic is encapsulated within the model, making it easy to change the prefix or the formatting without affecting the rest of the application. If the requirements change, you only need to update the logic in one place.</li>
<li><strong>Readability</strong>: For user interfaces or external communications, displaying a more descriptive ID can be more user-friendly and professional. It makes IDs easier to read, communicate, and reference.</li>
</ul>
<p>However, it is important to note that this method has some limitations and considerations:</p>
<ul>
<li><strong>Data Integrity</strong>: The <code>prefixed_id</code> is not stored in the database as such. Therefore, when querying or filtering data, you must use the original <code>id</code> field. The <code>prefixed_id</code> is suitable for display purposes and should be used in situations where a more descriptive identifier benefits the user experience.</li>
<li><strong>Security</strong>: Exposing your ID field might not be a good idea in the long run. It is recommended to carefully consider the security implications of exposing internal identifiers to external systems or users.</li>
</ul>
<hr>
<h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2>
<p>By integrating custom ID fields with Django admin and forms, you can enhance the management and usability of your Django applications. This approach ensures that your custom IDs are correctly displayed and handled in the admin interface and custom forms, providing users with a seamless experience while maintaining the integrity and uniqueness of your IDs.</p>
<p>Not only does this improve the administrative capabilities of your Django application, but it also aligns with best practices for web application development. This way, you can ensure that your application remains robust, scalable, and easy to maintain.</p>]]></content:encoded>
    </item>
    <item>
      <title>Mastering Reporting in Django with PostgreSQL Views</title>
      <link>https://www.sadig.dev/posts/postgresql-views-for-reporting</link>
      <guid isPermaLink="true">https://www.sadig.dev/posts/postgresql-views-for-reporting</guid>
      <pubDate>Tue, 30 Jan 2024 00:00:00 GMT</pubDate>
      <description>PostgreSQL views as first-class Django migrations — fewer surprises, faster reports, and a cleaner ORM surface area.</description>
      <category>django</category><category>postgres</category>
      <content:encoded><![CDATA[<p>PostgreSQL views are a powerful tool for creating virtual tables that can be used for reporting purposes. This article will explore using PostgreSQL views and Django migrations to create and update views. We will also discuss optimization techniques for improving the performance of views and how to work with complex views that involve joining multiple tables and using subqueries. By the end of this article, you will have a solid understanding of how to leverage PostgreSQL views for reporting purposes in your Django projects.</p>
<h2 id="introduction-to-postgresql-views"><a href="#introduction-to-postgresql-views">Introduction to PostgreSQL views</a></h2>
<h3 id="what-are-the-views-in-postgresql"><a href="#what-are-the-views-in-postgresql">What are the views in PostgreSQL?</a></h3>
<p>Views in PostgreSQL are virtual tables based on a query’s result. They allow you to encapsulate complex queries into a single object that can be treated like a table. <strong>Views</strong> provide a way to simplify the complexity of querying data by abstracting away the underlying query logic.</p>
<p>Views can be used to:</p>
<ul>
<li>Simplify complex queries by breaking them down into smaller, more manageable pieces.</li>
<li>Hide sensitive or confidential data by only exposing specific columns or rows.</li>
<li>Provide a consistent interface for accessing data, even if the underlying table structure changes.</li>
</ul>
<p>Views are handy when working with large and complex databases, as they allow you to organize and structure your data to make it easier to operate and understand.</p>
<blockquote>
<p>Tip: When creating views, it’s essential to consider the performance
implications. Views can introduce additional overhead, so optimizing them for the specific use case is necessary.</p>
</blockquote>
<h3 id="advantages-of-using-views"><a href="#advantages-of-using-views">Advantages of using views</a></h3>
<p>As a developer, <strong>one of the advantages</strong> of using views in PostgreSQL is the ability to abstract complex queries into a single, reusable object. This allows for cleaner, more maintainable code and improved performance.</p>
<p>Views also provide a way to <strong>simplify data access</strong> by presenting a logical representation of the underlying tables. This can be particularly useful when working with large and complex databases, as it allows us to focus on the specific data they need without understanding the underlying database structure. In my experience, these views are heavily used as a source for Microsoft Power BI or AWS QuickSight to visualize the data for dashboards.</p>
<p>Additionally, views can <strong>enhance security</strong> by limiting the data exposed to users. By granting access to views instead of tables, developers can control what data is visible and ensure that sensitive information is protected.</p>
<p>When working with views, it’s essential to remem<strong>ber</strong> that they are virtual tables and do not store any data. Instead, they provide a way to query and manipulate data from one or more tables in a structured and efficient manner.</p>
<h3 id="creating-views-in-postgresql"><a href="#creating-views-in-postgresql">Creating views in PostgreSQL</a></h3>
<p>Creating views in PostgreSQL is a straightforward process. Views are virtual tables that are based on the result of a query. They allow us to encapsulate complex queries and reuse them like tables. To create a view, we use the <code>CREATE VIEW</code> statement followed by the view name and the query that defines the view.</p>
<p>Here’s an example of creating a view that shows the total sales for each product:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">CREATE</span><span style="color:#D73A49;--shiki-dark:#F47067"> VIEW</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> product_sales</span><span style="color:#D73A49;--shiki-dark:#F47067"> AS</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">SELECT</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> product_id, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">SUM</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(quantity) </span><span style="color:#D73A49;--shiki-dark:#F47067">AS</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> total_sales</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">FROM</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> sales</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">GROUP BY</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> product_id;</span></span></code></pre>
<p>This view can then be used in other queries or joined with other tables to retrieve the total sales for each product.</p>
<h2 id="using-django-migrations-with-postgresql-views"><a href="#using-django-migrations-with-postgresql-views">Using Django migrations with PostgreSQL views</a></h2>
<h3 id="setting-up-django-project-with-postgresql"><a href="#setting-up-django-project-with-postgresql">Setting up Django project with PostgreSQL</a></h3>
<p>Setting up a Django project with PostgreSQL is a straightforward process. Here are the steps to follow:</p>
<ol>
<li>Install PostgreSQL and create a new database for your project. Or use Docker to setup a PostgreSQL database</li>
<li>Update the <code>DATABASES</code> setting in your Django project’s <code>settings.py</code> file to use PostgreSQL as the database backend.</li>
<li>Install the <code>psycopg2-binary</code> package, which is the PostgreSQL adapter for Python.</li>
<li>Run the Django migrations to create the necessary tables in the database.</li>
</ol>
<p>My Docker Compose setup for this tutorial:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">version</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">"3"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># external services to connect to</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">services</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">  postgres</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    image</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">postgres:12</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    container_name</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">tutorial_postgres</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    restart</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">always</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    volumes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      - </span><span style="color:#032F62;--shiki-dark:#96D0FF">tutorial-postgres:/var/lib/postgresql/data</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    ports</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      - </span><span style="color:#032F62;--shiki-dark:#96D0FF">"5439:5432"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    env_file</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">.env</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">    healthcheck</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      test</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: [</span><span style="color:#032F62;--shiki-dark:#96D0FF">"CMD-SHELL"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">]</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      interval</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">5s</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      timeout</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">5s</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">      retries</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">volumes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#8DDB8C">  tutorial-postgres</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span></code></pre>
<p>Django Settings:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">DATABASES</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">    "default"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "ENGINE"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: </span><span style="color:#032F62;--shiki-dark:#96D0FF">"django.db.backends.postgresql_psycopg2"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "NAME"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: os.environ.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"POSTGRES_DB"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "USER"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: os.environ.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"POSTGRES_USER"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "PASSWORD"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: os.environ.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"POSTGRES_PASSWORD"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "HOST"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: os.environ.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"POSTGRES_HOST"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "PORT"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">: os.environ.get(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"POSTGRES_PORT"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">}</span></span></code></pre>
<p>For demonstration purposes I’ve created <code>orders</code> app, and added models:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.conf </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> settings</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Customer</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    user </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.OneToOneField(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        settings.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">AUTH_USER_MODEL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#F69D50">        on_delete</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">models.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CASCADE</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    )</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    address </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.TextField()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.user.username</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Order</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    customer </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.ForeignKey(Customer, </span><span style="color:#E36209;--shiki-dark:#F69D50">on_delete</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">models.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CASCADE</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    product_name </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_length</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">100</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    quantity </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.IntegerField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    price </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.product_name</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF"> - </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.quantity</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Invoice</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.OneToOneField(Order, </span><span style="color:#E36209;--shiki-dark:#F69D50">on_delete</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">models.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CASCADE</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    invoice_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    total_amount </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"Invoice for Order: </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.order.id</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span></code></pre>
<p>Once you have completed these steps, your Django project will be set up to work with PostgreSQL. You can now start creating and using PostgreSQL views for reporting purposes.</p>
<h3 id="creating-a-migration-for-a-postgresql-view"><a href="#creating-a-migration-for-a-postgresql-view">Creating a migration for a PostgreSQL view</a></h3>
<p>When creating a migration for a view in Django, it is important to understand the steps involved. First, you need to create a new migration file using the <code>makemigrations</code> command. This command will generate a new migration file that includes the necessary SQL statements
to create the view. For detailed explanation check the <a href="https://docs.djangoproject.com/en/dev/topics/migrations/#data-migrations">Django’s official documentation</a>.</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># command</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># python manage.py makemigrations --empty &#x3C;yourappname> --name &#x3C;migration_name></span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># example</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">python</span><span style="color:#032F62;--shiki-dark:#96D0FF"> manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> makemigrations</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --empty</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --name</span><span style="color:#032F62;--shiki-dark:#96D0FF"> raw_sql_view</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Migrations </span><span style="color:#D73A49;--shiki-dark:#F47067">for</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'orders'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   orders/migrations/0002_raw_sql_view.py</span></span></code></pre>
<p>Next, you can define the view using the <code>migrations.RunSQL</code> operation in the migration file. This operation allows you to execute raw SQL statements to create the view.</p>
<p>Here’s an example:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> migrations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">migrations</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    dependencies </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        (</span><span style="color:#032F62;--shiki-dark:#96D0FF">'orders'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">'0001_initial'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    operations </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        migrations.RunSQL(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            'CREATE VIEW my_simple_view AS SELECT * FROM orders_invoice'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            'DROP VIEW my_simple_view'</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        ),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span></code></pre>
<p>Note that you need to specify both the SQL statement to create the view and the SQL statement to drop the view in case the migration needs to be rolled back.</p>
<p>Once the migration file is created, you can apply it using the <code>migrate</code> command. This will execute the SQL statements in the migration file and create the view in the database.</p>
<p><img src="/images/media/postgre_views/Screenshot_2024-01-31_at_02.29.52.png" alt="Screenshot 2024-01-31 at 02.29.52.png"></p>
<p>It is important to keep in mind that views are read-only, so you won’t be able to perform any write operations on them. If you need to update the view, you will need to create a new migration file that includes the necessary SQL statements to update the view.</p>
<h3 id="updating-views-with-migrations"><a href="#updating-views-with-migrations">Updating views with migrations</a></h3>
<p>Updating views with migrations is a straightforward process in Django. Once a view has been created, any changes to the underlying tables or columns can be easily reflected in the view using migrations.</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">python</span><span style="color:#032F62;--shiki-dark:#96D0FF"> manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> makemigrations</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --empty</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --name</span><span style="color:#032F62;--shiki-dark:#96D0FF"> update_raw_sql_view</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Migrations </span><span style="color:#D73A49;--shiki-dark:#F47067">for</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'orders'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   orders/migrations/0003_update_raw_sql_view.py</span></span></code></pre>
<p>To update a view with migrations, you can create a new migration file using the <code>makemigrations</code> command. In the migration file, you can use the <code>RunSQL</code> operation to execute the SQL statement that updates the view.</p>
<p>Here’s an example of how to update a view using migrations:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> migrations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">migrations</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    dependencies </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        (</span><span style="color:#032F62;--shiki-dark:#96D0FF">"orders"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"0002_raw_sql_view"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    operations </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        migrations.RunSQL(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            """</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            CREATE OR REPLACE VIEW my_simple_view AS</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            SELECT </span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                inv.id,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                inv.invoice_date,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                inv.total_amount,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                inv.order_id,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                ord.product_name,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                ord.quantity,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                ord.price,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                ord.order_date,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                usr.first_name,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                usr.last_name</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            FROM </span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                orders_invoice inv</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            INNER JOIN </span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                orders_order ord ON inv.order_id = ord.id</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            INNER JOIN </span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                orders_customer cust ON ord.customer_id = cust.id</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            INNER JOIN </span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                auth_user usr ON cust.user_id = usr.id;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            """</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            "DROP VIEW my_simple_view"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        ),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span></code></pre>
<p>As you can see when we want to have anything meaningful to show, SQL queries became more complex. But what if we could take advantage of Django ORM, to create PostgreSQL views?</p>
<h2 id="working-with-complex-views-in-django-migrations"><a href="#working-with-complex-views-in-django-migrations">Working with complex views in Django migrations</a></h2>
<h3 id="using-django-orm-in-migrations"><a href="#using-django-orm-in-migrations">Using Django ORM in migrations</a></h3>
<p>As a developer working with PostgreSQL views in Django migrations, one common scenario is the need to join multiple tables in a view. This allows us to combine data from different tables into a single virtual table that can be queried efficiently.</p>
<p>Create a new migration:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">python</span><span style="color:#032F62;--shiki-dark:#96D0FF"> manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> makemigrations</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --empty</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --name</span><span style="color:#032F62;--shiki-dark:#96D0FF"> django_orm_sql_view</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Migrations </span><span style="color:#D73A49;--shiki-dark:#F47067">for</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'orders'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   orders/migrations/0004_django_orm_sql_view.py</span></span></code></pre>
<p>Example of a migration with Django ORM:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> migrations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> generate_complex_qs</span><span style="color:#24292E;--shiki-dark:#ADBAC7">():</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> orders.models </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Invoice</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db.models </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> F, Value</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db.models.functions </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Concat</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    qs </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Invoice.objects.annotate(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#F69D50">        customer_full_name</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">Concat(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            F(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"order__customer__user__first_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            Value(</span><span style="color:#032F62;--shiki-dark:#96D0FF">" "</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            F(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"order__customer__user__last_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        ),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ).values(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "id"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "invoice_date"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "total_amount"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "customer_full_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__customer__address"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__product_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__quantity"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__price"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__order_date"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    )</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> qs</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> convert_qs_to_sql</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(qs):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> connections</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    cursor </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> connections[</span><span style="color:#032F62;--shiki-dark:#96D0FF">"default"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">].cursor()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    sql_query </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> cursor.mogrify(</span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">qs.query.sql_with_params()).decode(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"utf-8"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"ignore"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> sql_query</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "invoices_report"</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_SQL</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> convert_qs_to_sql(generate_complex_qs())</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">CREATE_POSTGRES_VIEW_SQL</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">CREATE OR REPLACE VIEW </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF"> AS</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#F47067"> {</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_SQL</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">REVERT_POSTGRES_VIEW_SQL</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">DROP VIEW IF EXISTS </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">migrations</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    dependencies </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        (</span><span style="color:#032F62;--shiki-dark:#96D0FF">"orders"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"0003_update_raw_sql_view"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    operations </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [migrations.RunSQL(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CREATE_POSTGRES_VIEW_SQL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">REVERT_POSTGRES_VIEW_SQL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)]</span></span></code></pre>
<p>Again to create the you should run <code>migrate</code>. Afterwards you can check if view created:</p>
<p><img src="/images/media/postgre_views/Screenshot_2024-01-31_at_03.39.32.png" alt="Screenshot 2024-01-31 at 03.39.32.png"></p>
<h3 id="using-django-models-to-access-postgres-views"><a href="#using-django-models-to-access-postgres-views">Using Django Models to access Postgres Views</a></h3>
<p>Sometimes you’ll need to expose Postgres Views via API or use in your application. Thanks to Django Models you can do that easily, just defining <code>managed=False</code> in your models:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> InvoiceReport</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">    id</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.BigAutoField(</span><span style="color:#E36209;--shiki-dark:#F69D50">primary_key</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">True</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    total_amount </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    quantity </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.IntegerField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    product_name </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_length</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">100</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    price </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    invoice_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    customer_full_name </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    address </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.TextField()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Meta</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        managed </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> False</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        db_table </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "invoices_report"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.customer_full_name</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF"> - </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.id</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span></code></pre>
<p>Django will require you to run <code>makemigrations</code> and <code>migrate</code> for your newly created <code>InvoiceReport</code> model, but under the hood it won’t try to re-create table or view:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">./manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> makemigrations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Migrations </span><span style="color:#D73A49;--shiki-dark:#F47067">for</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'orders'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   orders/migrations/0005_invoicereport.py</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">     - Create model InvoiceReport</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">./manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> migrate</span><span style="color:#24292E;--shiki-dark:#ADBAC7">       </span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Operations to perform:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   Apply all migrations: admin, auth, contenttypes, orders, sessions</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Running migrations:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   Applying orders.0005_invoicereport... OK</span></span></code></pre>
<p>Afterwards you can use <code>InvoiceReport</code> as usual Django Models:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">./manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> shell</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> </span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">Type</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "help",</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "copyright",</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "credits"</span><span style="color:#032F62;--shiki-dark:#96D0FF"> or</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "license"</span><span style="color:#032F62;--shiki-dark:#96D0FF"> for</span><span style="color:#032F62;--shiki-dark:#96D0FF"> more</span><span style="color:#032F62;--shiki-dark:#96D0FF"> information.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#F69D50">InteractiveConsole</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span><span style="color:#6F42C1;--shiki-dark:#F69D50">from</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders.models</span><span style="color:#032F62;--shiki-dark:#96D0FF"> import</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> *</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">InvoiceReport.objects.all</span><span style="color:#24292E;--shiki-dark:#ADBAC7">()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">&#x3C;</span><span style="color:#24292E;--shiki-dark:#ADBAC7">QuerySet [</span><span style="color:#D73A49;--shiki-dark:#F47067">&#x3C;</span><span style="color:#24292E;--shiki-dark:#ADBAC7">InvoiceReport: John Doe - </span><span style="color:#D73A49;--shiki-dark:#F47067">2></span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#D73A49;--shiki-dark:#F47067">&#x3C;</span><span style="color:#24292E;--shiki-dark:#ADBAC7">InvoiceReport: John Doe - </span><span style="color:#D73A49;--shiki-dark:#F47067">1></span><span style="color:#24292E;--shiki-dark:#ADBAC7">]</span><span style="color:#D73A49;--shiki-dark:#F47067">></span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span><span style="color:#D73A49;--shiki-dark:#F47067">for</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> report </span><span style="color:#D73A49;--shiki-dark:#F47067">in</span><span style="color:#032F62;--shiki-dark:#96D0FF"> InvoiceReport.objects.all</span><span style="color:#24292E;--shiki-dark:#ADBAC7">()</span><span style="color:#032F62;--shiki-dark:#96D0FF">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">...</span><span style="color:#032F62;--shiki-dark:#96D0FF">     print</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#F69D50">report.id,</span><span style="color:#032F62;--shiki-dark:#96D0FF"> report.product_name,</span><span style="color:#032F62;--shiki-dark:#96D0FF"> report.customer_full_name,</span><span style="color:#032F62;--shiki-dark:#96D0FF"> report.total_amount</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">...</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> </span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">1</span><span style="color:#032F62;--shiki-dark:#96D0FF"> product</span><span style="color:#6A737D;--shiki-dark:#768390"> #2 John Doe 10.10</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">2</span><span style="color:#032F62;--shiki-dark:#96D0FF"> product</span><span style="color:#032F62;--shiki-dark:#96D0FF"> name</span><span style="color:#6A737D;--shiki-dark:#768390"> #1 John Doe 15.98</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>></span></span></code></pre>
<h3 id="rolling-back-view-migrations"><a href="#rolling-back-view-migrations">Rolling back view migrations</a></h3>
<p>Rolling back view migrations is a straightforward process in Django. When a view migration is rolled back, the corresponding view is dropped from the database.</p>
<p>To roll back a view migration, you can use the <code>migrate</code> command by passing the number or name of the previous migration. For detailed explanation check <a href="https://docs.djangoproject.com/en/5.0/topics/migrations/#reversing-migrations">Django’s Documentation</a>.</p>
<p>For example:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#768390"># python manage.py migrate &#x3C;yourappname> &#x3C;migration_name></span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">python</span><span style="color:#032F62;--shiki-dark:#96D0FF"> manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> migrate</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 0003_update_raw_sql_view</span></span></code></pre>
<p>It’s important to note that rolling back a view migration will result in the loss of any data stored in the view. If you need to preserve the data in the view, you can create a new migration that recreates the view and then migrate to that new migration.</p>
<blockquote>
<p>Tip: Before rolling back a view migration, make sure to backup any important data stored in the view to avoid data loss.</p>
</blockquote>
<h2 id="optimizing-postgresql-views-for-reporting-purposes"><a href="#optimizing-postgresql-views-for-reporting-purposes">Optimizing PostgreSQL views for reporting purposes</a></h2>
<h3 id="choosing-the-correct-columns-for-the-view"><a href="#choosing-the-correct-columns-for-the-view">Choosing the correct columns for the view</a></h3>
<p>As a developer, when creating a PostgreSQL view for reporting purposes, it is important to carefully choose the columns that will be included in the view. <strong>Selecting the right columns</strong> can significantly impact the performance and usability of the view.</p>
<p>One approach is to include only the necessary columns that are relevant to the reporting requirements. This helps to minimize the amount of data retrieved from the database and improves query performance.</p>
<p>Additionally, consider including any calculated or derived columns that are commonly used in reporting. These columns can be pre-computed in the view, reducing the need for complex calculations in the reporting queries.</p>
<p>To summarize, when choosing the columns for a PostgreSQL view, focus on selecting the necessary columns and including any calculated or derived columns that are frequently used in reporting queries.</p>
<p>Here is a bulleted list of points to consider:</p>
<ul>
<li>Include only necessary columns</li>
<li>Include calculated or derived columns</li>
<li>Minimize data retrieval</li>
<li>Optimize query performance</li>
<li>Pre-compute commonly used calculations</li>
</ul>
<blockquote>
<p>Tip: By carefully selecting the columns for the view, you can improve the efficiency and effectiveness of your reporting queries.</p>
</blockquote>
<h3 id="choosing-materialized-views-over-views"><a href="#choosing-materialized-views-over-views">Choosing Materialized Views over Views</a></h3>
<p>When dealing with large datasets, views can simplify your code, but they don’t necessarily save you time during execution because their speed depends on the underlying query. This limitation becomes more evident with expensive queries and extensive datasets, presenting a drawback.</p>
<p>To address this concern for performance, a better alternative could be utilizing materialized views. These views allow you to store the query’s results on disk in a temporary table, acting as a cache. Consequently, querying the materialized view becomes much faster.</p>
<p>However, one drawback of materialized views is that they don’t automatically update when the data in the base tables changes. For instance, if a customer changes their address in the earlier example, we would see the updated information once we refresh the materialized view. This process involves rerunning the original query and caching the new results. An example demonstrating this situation will be presented in the next section.</p>
<p>Let’s create an example materializer view:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">python</span><span style="color:#032F62;--shiki-dark:#96D0FF"> manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> makemigrations</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --empty</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> --name</span><span style="color:#032F62;--shiki-dark:#96D0FF"> django_orm_materialized_view</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Migrations </span><span style="color:#D73A49;--shiki-dark:#F47067">for</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'orders'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">>></span><span style="color:#24292E;--shiki-dark:#ADBAC7">   orders/migrations/0006_django_orm_materialized_view.py</span></span></code></pre>
<p>Migration file:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> migrations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> generate_complex_qs</span><span style="color:#24292E;--shiki-dark:#ADBAC7">():</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> orders.models </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Invoice</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db.models </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> F, Value</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db.models.functions </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Concat</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    qs </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Invoice.objects.annotate(</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#F69D50">        customer_full_name</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7">Concat(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            F(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"order__customer__user__first_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            Value(</span><span style="color:#032F62;--shiki-dark:#96D0FF">" "</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            F(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"order__customer__user__last_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        ),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ).values(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "id"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "invoice_date"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "total_amount"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "customer_full_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__customer__address"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__product_name"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__quantity"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__price"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">        "order__order_date"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    )</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> qs</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> convert_qs_to_sql</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(qs):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> connections</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    cursor </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> connections[</span><span style="color:#032F62;--shiki-dark:#96D0FF">"default"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">].cursor()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    sql_query </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> cursor.mogrify(</span><span style="color:#D73A49;--shiki-dark:#F47067">*</span><span style="color:#24292E;--shiki-dark:#ADBAC7">qs.query.sql_with_params()).decode(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"utf-8"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"ignore"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> sql_query</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "invoices_materialized_report"</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_SQL</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> convert_qs_to_sql(generate_complex_qs())</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">CREATE_POSTGRES_VIEW_SQL</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">CREATE MATERIALIZED VIEW </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF"> AS</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#F47067"> {</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_SQL</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">CREATE UNIQUE INDEX ON </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF"> (id);</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">REVERT_POSTGRES_VIEW_SQL</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">DROP MATERIALIZED VIEW IF EXISTS </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">VIEW_NAME</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">migrations</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    dependencies </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        (</span><span style="color:#032F62;--shiki-dark:#96D0FF">"orders"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">"0005_invoicereport"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    operations </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [migrations.RunSQL(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CREATE_POSTGRES_VIEW_SQL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">REVERT_POSTGRES_VIEW_SQL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)]</span></span></code></pre>
<p>As you probably noticed, I created an <code>INDEX</code> on view. It’s required to be able to <code>REFRESH</code> the materialized views.</p>
<p>Result:</p>
<p><img src="/images/media/postgre_views/Screenshot_2024-01-31_at_04.34.43.png" alt="Screenshot 2024-01-31 at 04.34.43.png"></p>
<h3 id="refreshing-materialized-views"><a href="#refreshing-materialized-views">Refreshing materialized views</a></h3>
<p>Refreshing materialized views is an important step in ensuring that the data in the view is up to date. Materialized views are a great way to improve query performance by precomputing and storing the results of a query. However, the data in a materialized view can become stale over time as the underlying data changes. To refresh a materialized view in PostgreSQL, you can use the <code>REFRESH MATERIALIZED VIEW</code> command.</p>
<p>Here is an example of how to refresh a materialized view:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">REFRESH MATERIALIZED VIEW my_materialized_view;</span></span></code></pre>
<p>This command will recompute the data in the materialized view based on the underlying tables and update the view with the latest data. It’s important to note that refreshing a materialized view can be an expensive operation, especially if the view contains a large amount of data or complex calculations. Therefore, it’s recommended to schedule regular refreshes based on the frequency of data changes and the performance requirements of your application.</p>
<p>To schedule regular refreshes of a materialized view, you can use the PostgreSQL <code>REFRESH MATERIALIZED VIEW CONCURRENTLY</code> command. This command allows you to refresh the view without locking the view, allowing concurrent reads and writes to the view while the refresh is in progress.</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">REFRESH MATERIALIZED VIEW CONCURRENTLY my_materialized_view;</span></span></code></pre>
<p>By using the <code>CONCURRENTLY</code> option, you can minimize the impact on the performance of your application while keeping the materialized view up to date.</p>
<p>Let’s implement a refresh mechanism into our Django Model, so we can refresh in our app whenever we need a fresh data:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> InvoiceMaterializedReport</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">models</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Model</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">    id</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.BigAutoField(</span><span style="color:#E36209;--shiki-dark:#F69D50">primary_key</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">True</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    total_amount </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    quantity </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.IntegerField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    product_name </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_length</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">100</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    price </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DecimalField(</span><span style="color:#E36209;--shiki-dark:#F69D50">max_digits</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">10</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#E36209;--shiki-dark:#F69D50">decimal_places</span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">2</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    order_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    invoice_date </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.DateField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    customer_full_name </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.CharField()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    address </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> models.TextField()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Meta</span><span style="color:#24292E;--shiki-dark:#ADBAC7">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        managed </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> False</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        db_table </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "invoices_materialized_report"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> __str__</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        return</span><span style="color:#D73A49;--shiki-dark:#F47067"> f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.customer_full_name</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF"> - </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">self</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.id</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#DCBDFB">    @</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">classmethod</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    def</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> refresh_view</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(cl):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">        with</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> connection.cursor() </span><span style="color:#D73A49;--shiki-dark:#F47067">as</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> cursor:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            cursor.execute(</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">                f</span><span style="color:#032F62;--shiki-dark:#96D0FF">"REFRESH MATERIALIZED VIEW CONCURRENTLY </span><span style="color:#005CC5;--shiki-dark:#F47067">{</span><span style="color:#24292E;--shiki-dark:#ADBAC7">cl._meta.db_table</span><span style="color:#005CC5;--shiki-dark:#F47067">}</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">            )</span></span>
<span class="line"></span></code></pre>
<p>Usage example:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#F69D50">./manage.py</span><span style="color:#032F62;--shiki-dark:#96D0FF"> shell</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span><span style="color:#6F42C1;--shiki-dark:#F69D50">from</span><span style="color:#032F62;--shiki-dark:#96D0FF"> orders.models</span><span style="color:#032F62;--shiki-dark:#96D0FF"> import</span><span style="color:#005CC5;--shiki-dark:#6CB6FF"> *</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>> </span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">InvoiceMaterializedReport.refresh_view</span><span style="color:#24292E;--shiki-dark:#ADBAC7">()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">>>></span></span></code></pre>
<h3 id="handling-view-dependencies"><a href="#handling-view-dependencies">Handling view dependencies</a></h3>
<p>When working with complex views in Django migrations, it is important to handle view dependencies properly. This ensures that the views are created and updated in the correct order, avoiding any errors or inconsistencies.</p>
<p>One way to handle view dependencies is by using the <code>depends_on</code> attribute in the migration file. This attribute specifies the views that the current view depends on, ensuring that the dependencies are resolved before creating or updating the view.</p>
<p>For example, let’s say we have two views: <code>view1</code> and <code>view2</code>. If <code>view2</code> depends on <code>view1</code>, we can specify this dependency in the migration file for <code>view2</code> using the <code>depends_on</code> attribute:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">from</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> django.db </span><span style="color:#D73A49;--shiki-dark:#F47067">import</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> migrations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">class</span><span style="color:#6F42C1;--shiki-dark:#F69D50"> Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">migrations</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#6F42C1;--shiki-dark:#6CB6FF">Migration</span><span style="color:#24292E;--shiki-dark:#ADBAC7">):</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    dependencies </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        (</span><span style="color:#032F62;--shiki-dark:#96D0FF">'app_name'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">'0001_initial'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        (</span><span style="color:#032F62;--shiki-dark:#96D0FF">'app_name'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">, </span><span style="color:#032F62;--shiki-dark:#96D0FF">'0002_view1_migration'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    operations </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        migrations.RunSQL(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            'CREATE VIEW view2 AS SELECT * FROM view1'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">            'DROP VIEW view2'</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        ),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    ]</span></span></code></pre>
<p>By specifying the dependency on <code>view1</code> in the <code>dependencies</code> list, Django ensures that <code>view1</code> is created or updated before creating or updating <code>view2</code>.</p>
<p>Handling view dependencies correctly is crucial for maintaining the integrity and consistency of the database schema.</p>
<h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2>
<p>In conclusion, PostgreSQL views provide a powerful tool for creating custom reports in Django applications. By leveraging the capabilities of Django migrations, developers can easily define and manage views that can be used for reporting purposes. Views allow for efficient data retrieval and can be customized to meet specific reporting requirements. With the ability to perform complex queries and join multiple tables, views offer a flexible solution for generating meaningful insights from the database. <strong>PostgreSQL views</strong> are a valuable addition to any Django project, enabling developers to create comprehensive reports and analyze data in a structured and efficient manner.</p>
<h3 id="key-takeaways"><a href="#key-takeaways">Key Takeaways</a></h3>
<ul>
<li>PostgreSQL views are virtual tables that can be used for reporting purposes.</li>
<li>You can create and update views in your PostgreSQL database using Django migrations.</li>
<li>Optimizing views involves choosing the correct columns, filtering and aggregating data, and using indexes for improved performance.</li>
<li>Complex views can be created by joining multiple tables and using subqueries.</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Capture Movement Like a Pro — Motion Detection in JavaScript</title>
      <link>https://www.sadig.dev/posts/motion-detection-using-javascript</link>
      <guid isPermaLink="true">https://www.sadig.dev/posts/motion-detection-using-javascript</guid>
      <pubDate>Sun, 05 Aug 2018 00:00:00 GMT</pubDate>
      <description>Turn a webcam into a tripwire with two canvases, a frame diff, and surprisingly little math.</description>
      <category>js</category><category>experiment</category>
      <content:encoded><![CDATA[<h2 id="case"><a href="#case">Case</a></h2>
<p>The client had a request: Is it possible to determine how energetically people are dancing?</p>
<p>The idea behind it was to measure people’s energy levels. As you may already guess, it was an ad campaign for one of the energy drink brands.</p>
<p>The campaign wasn’t designed to be an online campaign. The plan was that people will be dancing on the stage that was built for the ad campaign, and our software should be measuring their energy level during the dance in real-time.</p>
<p>But how?</p>
<h2 id="building"><a href="#building">Building</a></h2>
<p>I’ve tried to design a system where high-speed internet is not an essential part of the software. If once we were able to open a page and javascript was loaded, it meant we’ll be able to operate as intended. Reducing the connection to the server meant most of the heavy lifting had to be done in the front end by Javascript. (after technical details, I’ll come back to this method and explain how it saved our operation big time)</p>
<p>This is how I designed the system:</p>
<p><img src="https://i.imgur.com/Quex4Xp.png" alt="https://i.imgur.com/Quex4Xp.png"></p>
<p>The goal was every dancer should see his result in real-time on the big screen, to know how well he/she’s doing. In this way, the person performing would know if he/she should speed up or can slow down a bit if he/she is out of breath.</p>
<p>To achieve this, we had to show a progress bar that changes according to the dancer’s performance. And at the end of the performance, we had to send a recorded act of dance with the progress bar’s fluctuations to the server to store.</p>
<p>And last but not least, we had to be able to control the software remotely, because we shouldn’t interact with the notebook that mirrors the big screen.</p>
<p>Once requirements were established, it was time to build the actual system.</p>
<p>To determine and rate the energy level of the dancer, I decided to use motion detection technology. In other words, if there are too many movements in the frame, it’s probably an energetic dance. And to detect the motion using Javascript meant I had to compare every frame the camera sent me to see if there were enough movements and, if so, check how many pixels changed to rate the performance.</p>
<p>To rate the performance, I’ve set the minimum threshold to check if the amount of pixel changes were greater than the threshold, which meant the person on camera dancing energetically enough, and added 1% to his progress if pixel changes were below the threshold I penalized dancer by reducing his progress by 1%. This meant if the dancer decided to stop for a while to take a breath, his progress would keep declining. It would continue until the dancer hit 100%, which meant he won.</p>
<p>Suppose you want to dig a little deeper to understand the technical background of motion detection; I strongly recommend you to check out the articles I mentioned at the end of this note. Once you’ve understood how things work under the hood, you won’t feel like you’re shooting in the dark when you’re using a library. For example, I’ve used “<a href="https://github.com/lonekorean/diff-cam-engine">diff-cam-engine</a>” for motion detection.</p>
<p>When I was done with the game mechanism of the app, now I had to work on the part where I had to record the performance video, merge it with the progress bar animation, and add the music to the background, which played when the dancer performed.</p>
<p>To do it, I’ve decided to use hidden canvas, where I drew a video and the animation of the progress bar side by side in real-time. When the performer finished the act, I added the music to the background using WebRTC and sent the final product to the server. This was the only time I’d sent a huge chunk of data to the backend. (I’ve listed tutorials and code examples of the WebRTC at the end of this note)</p>
<p>Example of the canvas recording:</p>
<div class="video-embed">
  <iframe src="https://www.youtube-nocookie.com/embed/2Ifv9e73su8" title="Canvas recording demo" loading="lazy" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<p>After dealing with the video editing part of the app, it was time to handle the remote control part of the system. If you remember, one of the requirements was that we shouldn’t interact with the monitor screen in any way. To achieve seamless integration with the backend, sockets came to the rescue. The front end of the app listened to the socket to receive the new player’s data and start and stop the command of the game. These pieces of information were sent from the app’s control panel, which listened to the socket to receive info about the process that happened on the app’s front end.</p>
<p>When it comes to the backend, Python is my go-to language. Because of Django’s solid structure and built-in features, I’ve decided to use Django at the backend this time as well. And handling the socket connections was fairly easy, too, because of Django channels. A simple Django setup inside the docker container did the trick. Here’s the setup example that I’ve used.</p>
<p><img src="https://i.imgur.com/sdPa3tU.png" alt="https://i.imgur.com/sdPa3tU.png"></p>
<h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2>
<p>As you may already know, when you’re in the field, always something goes wrong. For example, in our situation, on the 2nd day of the event, the internet connection was so poor that uploading recorded videos to the server was a headache. And luckily, because of the system’s architecture and most of the processes being handled by javascript on the front side of the app, it wasn’t a big deal. We just turned off the video recording and uploading them to the server from the admin panel, and we were good to go. The app’s usage didn’t interrupt, and everybody was happy.</p>
<p>I was already familiar with video editing with FFmpeg at the backend and recording webcams with the help of ActionScript and Nginx RTMP. Still, the result of working on videos with Javascript was surprisingly positive too. And I’m looking forward to using it in future projects as well.</p>
<p>In conclusion, the project was completed successfully.</p>
<p>Below is a list of the articles and libraries that I looked into before starting the project may be helpful in your situation as well:</p>
<p><em>Libraries:</em></p>
<ul>
<li><a href="https://github.com/lonekorean/diff-cam-engine">https://github.com/lonekorean/diff-cam-engine</a></li>
<li><a href="https://github.com/beije/motion-detection-in-javascript">https://github.com/beije/motion-detection-in-javascript</a></li>
<li><a href="https://github.com/ReallyGood/js-motion-detection">https://github.com/ReallyGood/js-motion-detection</a></li>
<li><a href="https://trackingjs.com/examples/face_camera.html">https://trackingjs.com/examples/face_camera.html</a></li>
<li><a href="https://github.com/hdmchl/gest.js">https://github.com/hdmchl/gest.js</a></li>
<li><a href="https://github.com/maniart/diffyjs">https://github.com/maniart/diffyjs</a></li>
<li><a href="https://github.com/collab-project/videojs-record">https://github.com/collab-project/videojs-record</a></li>
<li><a href="https://recordrtc.org/">https://recordrtc.org/</a></li>
<li><a href="https://github.com/spite/ccapture.js/">https://github.com/spite/ccapture.js/</a></li>
<li><a href="https://github.com/Stanko/html-canvas-video-player">https://github.com/Stanko/html-canvas-video-player</a></li>
</ul>
<p><em>Articles:</em></p>
<ul>
<li><a href="https://hackernoon.com/motion-detection-in-javascript-2614adea9325">https://hackernoon.com/motion-detection-in-javascript-2614adea9325</a></li>
<li><a href="https://codersblock.com/blog/motion-detection-with-javascript/">https://codersblock.com/blog/motion-detection-with-javascript/</a></li>
<li><a href="https://benjaminhorn.io/code/motion-detection-with-javascript-and-a-web-camera/">https://benjaminhorn.io/code/motion-detection-with-javascript-and-a-web-camera/</a></li>
<li><a href="https://www.adobe.com/devnet/archive/html5/articles/javascript-motion-detection.html">https://www.adobe.com/devnet/archive/html5/articles/javascript-motion-detection.html</a></li>
<li><a href="https://hasgeek.tv/jsfoo/2013-1/681-using-camera-motion-detection-in-js-for-gestures-based-interaction">https://hasgeek.tv/jsfoo/2013-1/681-using-camera-motion-detection-in-js-for-gestures-based-interaction</a></li>
<li><a href="https://webrtchacks.com/baby-motion-detector/">https://webrtchacks.com/baby-motion-detector/</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Serverless Twitter Bots with Google Apps Script</title>
      <link>https://www.sadig.dev/posts/serverless-twitter-bot-google-app-script</link>
      <guid isPermaLink="true">https://www.sadig.dev/posts/serverless-twitter-bot-google-app-script</guid>
      <pubDate>Fri, 22 Jun 2018 00:00:00 GMT</pubDate>
      <description>A tiny 2013-era hack, dusted off — using Apps Script as a free, persistent cron for OAuth-flavored side projects.</description>
      <category>js</category><category>experiment</category>
      <content:encoded><![CDATA[<h2 id="experiment"><a href="#experiment">Experiment</a></h2>
<p>Back in 2013, I was spending an unhealthy amount of time on Twitter. And at that time, I was really into the caricatures. So instead of checking every site I know for any new caricatures, I thought wouldn’t be great if there were a Twitter account that “tweets” new caricatures every day. And actually, there were such accounts. The problem was that those accounts weren’t relevant enough and shared the same caricatures over and over… And most of them were running actively only for a couple of months. Because after a couple of months, the owner of accounts usually got bored and didn’t want to continue.</p>
<p>So to have an account without an owner that gets bored quickly, I thought we should delegate this task to the bots.</p>
<h2 id="building"><a href="#building">Building</a></h2>
<p>Because it was a weekend project, I didn’t want it to cost an arm and leg. I knew that if I wanted the bot to be a long-running show, it should require a minimum of possible time and resources from me.</p>
<p>I was already familiar with Google Apps Script and had used it several times. So I thought if I could build the bot with Google Apps Script (GAS), it meant I wouldn’t be paying for servers, and as a bonus, I would get to play with the Javascript at the backend.</p>
<p>Obviously, it has been a long time since I started building it, and I can not recall all the process details. But I remember that there was built-in support for OAuth in GAS back then, so it was relatively easier to build a bot. And there was another service called ScriptDB in GAS. It allowed scripts to have databases and store data in them. For example, I’ve used ScriptDB to keep track of the caricatures to check if the bot has tweeted the picture before.</p>
<p>But both of those services were deprecated; I had to replace those parts along the way to keep the bot functioning. So when ScriptDB was deprecated, I started using Parse.com. Do you remember old, good Parse.com? The one that Facebook bought and killed after a while.</p>
<p>After the fate of Parse.com, I gave up on third-party databases, and since then, I’ve been using Google Sheets as a database for the bot.</p>
<p>And nowadays, you have to use libraries to authenticate with external services like Twitter:</p>
<ul>
<li><a href="https://github.com/gsuitedevs/apps-script-oauth1">https://github.com/gsuitedevs/apps-script-oauth1</a></li>
<li><a href="https://github.com/gsuitedevs/apps-script-oauth2">https://github.com/gsuitedevs/apps-script-oauth2</a></li>
</ul>
<p>Since the bot’s launch, it has seen a couple of iterations. Right now, it’s on 3rd version.</p>
<p>After the latest update, it now has a little web dashboard where you can manually post, check the time of scheduled events, or check the social channels’ connection status.</p>
<p><img src="https://i.imgur.com/YDYdzXd.gif" alt="https://i.imgur.com/YDYdzXd.gif"></p>
<p>This is how it works:</p>
<p><img src="https://i.imgur.com/1B0EUnI.png" alt="https://i.imgur.com/1B0EUnI.png"></p>
<p>You can set “triggers” on the Google Apps Script file. For example, I’ve set up a trigger that runs every hour and executes the GAS function that checks websites to see if any new caricatures are posted. When it finds a picture, it first runs it through the database (Google Sheets) to check if it’s a new picture. If so, then posts it to the social channels and write back to the database for future reference.</p>
<p>As you can see from the picture above, after figuring out how to integrate with Twitter, I’ve also incorporated a bot with Facebook and Tumblr. Tweeting or posting text was easy, but posting images with GAS took some time to figure out. So here’s the code snippet to save you time:</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">function</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> getTwitterService</span><span style="color:#24292E;--shiki-dark:#ADBAC7">() {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">  // Check https://github.com/gsuitedevs/apps-script-oauth1#usage</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">  // for the docs</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">  return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> OAuth1.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">createService</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'twitter'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">      // Set the endpoint URLs.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setAccessTokenUrl</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'https://api.twitter.com/oauth/access_token'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setRequestTokenUrl</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'https://api.twitter.com/oauth/request_token'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setAuthorizationUrl</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'https://api.twitter.com/oauth/authorize'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">      // Set the consumer key and secret.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setConsumerKey</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">TWITTER</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CONSUMER_KEY</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setConsumerSecret</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">TWITTER</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">CONSUMER_SECRET</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">      // Set the name of the callback function in the script referenced</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">      // above that should be invoked to complete the OAuth flow.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setCallbackFunction</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">'authCallback'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">      // Set the property store where authorized tokens should be persisted.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      .</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setPropertyStore</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(PropertiesService.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getUserProperties</span><span style="color:#24292E;--shiki-dark:#ADBAC7">());</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">function</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> tweetPicFromURL</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#E36209;--shiki-dark:#F69D50">url</span><span style="color:#24292E;--shiki-dark:#ADBAC7">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">  try</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> boundary </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "cuthere"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> picture </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> UrlFetchApp.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">fetch</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(url).</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getBlob</span><span style="color:#24292E;--shiki-dark:#ADBAC7">().</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">setContentTypeFromExtension</span><span style="color:#24292E;--shiki-dark:#ADBAC7">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> status </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> "Test status"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> requestBody </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> Utilities.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">newBlob</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"--"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">boundary</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                                        "Content-Disposition: form-data; name=</span><span style="color:#005CC5;--shiki-dark:#F47067">\"</span><span style="color:#032F62;--shiki-dark:#96D0FF">status</span><span style="color:#005CC5;--shiki-dark:#F47067">\"\r\n\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">status</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                                        "--"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">boundary</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                                        "Content-Disposition: form-data; name=</span><span style="color:#005CC5;--shiki-dark:#F47067">\"</span><span style="color:#032F62;--shiki-dark:#96D0FF">media[]</span><span style="color:#005CC5;--shiki-dark:#F47067">\"</span><span style="color:#032F62;--shiki-dark:#96D0FF">; filename=</span><span style="color:#005CC5;--shiki-dark:#F47067">\"</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">picture.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getName</span><span style="color:#24292E;--shiki-dark:#ADBAC7">()</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">\"\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#96D0FF">                                        "Content-Type: "</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">picture.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getContentType</span><span style="color:#24292E;--shiki-dark:#ADBAC7">()</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">\r\n\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getBytes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">();</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    requestBody </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> requestBody.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">concat</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(picture.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getBytes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">());</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    requestBody </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> requestBody.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">concat</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(Utilities.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">newBlob</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#005CC5;--shiki-dark:#F47067">\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">--"</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">boundary</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#032F62;--shiki-dark:#96D0FF">"--</span><span style="color:#005CC5;--shiki-dark:#F47067">\r\n</span><span style="color:#032F62;--shiki-dark:#96D0FF">"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">).</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">getBytes</span><span style="color:#24292E;--shiki-dark:#ADBAC7">());</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> options </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          method: </span><span style="color:#032F62;--shiki-dark:#96D0FF">"post"</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          contentType: </span><span style="color:#032F62;--shiki-dark:#96D0FF">"multipart/form-data; boundary="</span><span style="color:#D73A49;--shiki-dark:#F47067">+</span><span style="color:#24292E;--shiki-dark:#ADBAC7">boundary,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          payload: requestBody</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">    // twitter stuff</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> api </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#032F62;--shiki-dark:#96D0FF"> 'https://api.twitter.com/1.1/statuses/update_with_media.json'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> twitterService </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB"> getTwitterService</span><span style="color:#24292E;--shiki-dark:#ADBAC7">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    var</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> response </span><span style="color:#D73A49;--shiki-dark:#F47067">=</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> twitterService.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">fetch</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(api, options);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    Logger.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">log</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(response);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    return</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> response;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">  }</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">  catch</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(e){Logger.</span><span style="color:#6F42C1;--shiki-dark:#DCBDFB">log</span><span style="color:#24292E;--shiki-dark:#ADBAC7">(e)}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">}</span></span></code></pre>
<p>Github Gist of the code snippet: <a href="https://gist.github.com/msadig/30dccada8104adc823336eaadce1cc6c">https://gist.github.com/msadig/30dccada8104adc823336eaadce1cc6c</a></p>
<h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2>
<p>Honestly, up until this year’s (2018) Google I/O, I didn’t realize why this project appeals to me so much. But, after <a href="https://youtu.be/ogexnfng_hE?t=521">watching one</a> of the presentations on Google I/O ‘18, I realized that I like this architecture and project because it’s serverless. And the fact that I was using serverless before even it was a “thing” made me love this project even more now.</p>
<p>So after 5 years and 11.3K caricatures that posted, I would like to think about this experiment as a successful one.</p>
<p>Here’re the links to check out for working bots:</p>
<ul>
<li><a href="https://twitter.com/karikARTur">https://twitter.com/karikARTur</a></li>
<li><a href="http://fb.com/karikartur">http://fb.com/karikartur</a></li>
<li><a href="http://karikartur.tumblr.com/">http://karikartur.tumblr.com/</a></li>
</ul>
<p><em>Links:</em></p>
<ul>
<li><a href="https://gsuite-developers.googleblog.com/2014/05/deprecating-scriptdb-and-domain-service.html">https://gsuite-developers.googleblog.com/2014/05/deprecating-scriptdb-and-domain-service.html</a></li>
<li><a href="https://developers.google.com/apps-script/migration/oauth-config">https://developers.google.com/apps-script/migration/oauth-config</a></li>
<li><a href="https://techcrunch.com/2013/04/25/facebook-parse/">https://techcrunch.com/2013/04/25/facebook-parse/</a></li>
<li><a href="https://ctrlq.org/code/19408-create-bot">https://ctrlq.org/code/19408-create-bot</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Hosting a Site on the Cloud, Cheaply, with WordPress and Gatsby</title>
      <link>https://www.sadig.dev/posts/wordpress-gatsby-cheap-cloud-hosting</link>
      <guid isPermaLink="true">https://www.sadig.dev/posts/wordpress-gatsby-cheap-cloud-hosting</guid>
      <pubDate>Tue, 12 Jun 2018 00:00:00 GMT</pubDate>
      <description>A static front, a headless back, and a hosting bill that fits inside a coffee.</description>
      <category>js</category><category>experiment</category>
      <content:encoded><![CDATA[<h2 id="a-subject-of-the-experiment"><a href="#a-subject-of-the-experiment">A Subject of the Experiment</a></h2>
<p>When I say the cost-effective way of hosting a website, I don’t mean only the money side of hosting. It doesn’t make sense to me if I have to put a lot of time and effort into maintaining the site, even if I can host it for free. So when I was thinking about building a website to put my notes in, I knew I had to take into account a couple of things.</p>
<h2 id="serverless"><a href="#serverless">Serverless</a></h2>
<p>Maintenance of the website shouldn’t be my abundance. From my past experiences, I knew that it’s a pain in the but to maintain a blog to keep its plugins and versions up to date. There will be security patches and updates; you must do those things almost instantly; otherwise, your resource will be vulnerable. It was obvious that I should take a “serverless” approach.</p>
<p>So WordPress.com was the logical place to start. I would be responsible for neither maintaining server support nor updates of the CMS. And because I was already familiar with WordPress, there wouldn’t be any learning curve either.</p>
<h2 id="customization"><a href="#customization">Customization</a></h2>
<p>But when it comes to customizing the website, wordpress.com give little to none control over the blog. I would have some access to customize the blog if I’d choose a $8/month plan. But this option didn’t appeal to me too much. Not because it’s too expensive, but mainly because to be able to customize the site only from the WordPress Admin, and using just CSS, seemed bit limiting to me.</p>
<p>Although I wanted something minimalist, I still wanted to have total control over the website.</p>
<h2 id="stack"><a href="#stack">Stack</a></h2>
<p>When it came to choosing the stack, again, I wanted it not to be time-consuming. Which meant I wanted to build with the stack that I was already familiar with before. I already knew about the static website concept but never tried it. A few times, I’ve read about <a href="https://jekyllrb.com/">Jekyll</a> and how to use it, but it never appealed to me. The main reason was it didn’t feel intuitive to me to commit the blog post to the git. When I run these little experiments, I always try to think if it has real-world application. Meaning I never believed that I was gonna be able to find a blogger or content manager that easily, which going to adapt to the process of writing and formatting posts with the markdown and push it to the git as a commit.</p>
<p>Then one day, I stumbled upon <a href="https://www.gatsbyjs.org/">Gatsby</a>. The main attraction point for me was to have that many data sources. And, of course, React. That meant I could easily build a website using JAMStack with multitier architecture.</p>
<p>So architecture, I was thinking, was something like this:</p>
<p><img src="https://i.imgur.com/xOHaYeU.png" alt="https://i.imgur.com/xOHaYeU.png"></p>
<ul>
<li><a href="https://bitbucket.org/">Bitbucket.org</a> will serve as a git to store the project’s source code.</li>
<li><a href="https://wordpress.com/">WordPress.com</a> will serve as an API for the website.</li>
<li><a href="https://netlify.com/">Netlify.com</a> will handle the build process and will serve as hosting as well.</li>
<li><a href="https://www.cloudflare.com/">Cloudflare.com</a> will handle caching and DNS management. And as a bonus website is going to get a free SSL</li>
<li>And all of these are going to cost $0</li>
</ul>
<h2 id="so-i-started-to-build"><a href="#so-i-started-to-build">So I started to build</a></h2>
<p>This architecture allows me to keep the development and publishing environments separate. The editor or publisher works in the WordPress admin, and he/she does not have to commit or push the content changes. And with this structure, it’s possible to update the content even from a smartphone.</p>
<p>As you can see from the visual above, Gatsby is one of the most crucial parts of architecture. Gatsby is the glue that keeps together the whole scheme. Because of it, I can easily get the data from WordPress.com API and build customizable views for posts and pages. And because Gatsby uses <a href="https://graphql.org/">GraphQL</a> as a query language, it’s even more fun to work with it.</p>
<p>Gatsby’s official site covers all the steps well enough to know how to start and where to look. So I’m not going to copy and paste those things here. Instead, I encourage you to go and check the docs yourself: <a href="https://www.gatsbyjs.org/docs/">https://www.gatsbyjs.org/docs/</a></p>
<p>And because of I guilty of doing so, I highly recommend checking the community starters before starting the reinventing the wheel: <a href="https://www.gatsbyjs.org/docs/gatsby-starters/">https://www.gatsbyjs.org/docs/gatsby-starters/</a></p>
<p>But because tutorials usually show how to integrate with self-hosted WordPress sites, I’m gonna put a code piece below to save a couple of seconds of your life.</p>
<pre class="shiki shiki-themes github-light github-dark-dimmed" style="background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7" tabindex="0"><code><span class="line"><span style="color:#005CC5;--shiki-dark:#6CB6FF">module</span><span style="color:#24292E;--shiki-dark:#ADBAC7">.</span><span style="color:#005CC5;--shiki-dark:#6CB6FF">exports</span><span style="color:#D73A49;--shiki-dark:#F47067"> =</span><span style="color:#24292E;--shiki-dark:#ADBAC7"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">  ...</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">  plugins: [</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    ...</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      resolve: </span><span style="color:#032F62;--shiki-dark:#96D0FF">`gatsby-source-wordpress`</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      options: {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">        // your WordPress source</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        baseUrl: </span><span style="color:#032F62;--shiki-dark:#96D0FF">`your.wordpress.com`</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        protocol: </span><span style="color:#032F62;--shiki-dark:#96D0FF">`https`</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">        // is it hosted on wordpress.com, or self-hosted?</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        hostingWPCOM: </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">true</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">        // does your site use the Advanced Custom Fields Plugin?</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        useACF: </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">false</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        auth: {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">          // You'll get Client Secret and Client ID from https://developer.wordpress.com/apps/</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          wpcom_app_clientSecret: </span><span style="color:#032F62;--shiki-dark:#96D0FF">'client_secret_goes_here'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          wpcom_app_clientId: </span><span style="color:#032F62;--shiki-dark:#96D0FF">'client_id_goes_here'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#768390">          // Your WordPress.com user credentials</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          wpcom_user: </span><span style="color:#032F62;--shiki-dark:#96D0FF">'email@example.com'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">          wpcom_pass: </span><span style="color:#032F62;--shiki-dark:#96D0FF">'veryverystrongpassword'</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">        verboseOutput: </span><span style="color:#005CC5;--shiki-dark:#6CB6FF">true</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">      }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">    },</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F47067">    ...</span><span style="color:#24292E;--shiki-dark:#ADBAC7">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">  ]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#ADBAC7">}</span></span>
<span class="line"></span></code></pre>
<p>The rest is pretty straightforward. You connect your Bitbucket or Github account with your Netlify.com account and whenever you push a new code, Netlify builds it and hosts it for you: <a href="https://www.netlify.com/docs/continuous-deployment/">https://www.netlify.com/docs/continuous-deployment/</a></p>
<p>Now, it’s time to set up a webhook to notify Netlify.com when we publish new content. You can do this from your WP Admin dashboard. You can get to this dashboard by adding <code>/wp-admin</code> to the end of your site’s URL (e.g., your.wordpress.com/wp-admin). You can read detailed instructions on how to set up webhook here: <a href="https://en.support.wordpress.com/webhooks/">https://en.support.wordpress.com/webhooks/</a></p>
<p>And here you can learn how to get the URL for incoming hooks: <a href="https://www.netlify.com/docs/webhooks/">https://www.netlify.com/docs/webhooks/</a></p>
<p>Now we have set up the continuous deployment process, it’s time to automate one more thing. As you can see from the scheme above, we rely on Cloudflare for our CDN. This means Cloudflare will cache static files to decrease the latency of user requests. And because our site was built just with static files, Cloudflare will cache our whole site. And we have to clear that cache somehow after every successful deployment.</p>
<p>Once again, Netlify hooks come to the rescue. This time outgoing webhook. Set up a webhook that visits the Cloudflare API:</p>
<pre><code>https://www.cloudflare.com/api_json.html?a=fpurge_ts&#x26;tkn=&#x3C;TOKEN>&#x26;email=&#x3C;ACCOUNT_EMAIL>&#x26;z=&#x3C;DOMAIN2PURGE.COM>&#x26;v=1
</code></pre>
<p>Where <code>&#x3C;TOKEN></code> is the Cloudflare Global API Key, that you can find instructions on how to find that key here: <a href="https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-API-key-">https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-API-key-</a></p>
<p><img src="https://i.imgur.com/7oeEhnq.png" alt="https://i.imgur.com/7oeEhnq.png"></p>
<p>That’s it. Now the flow is fully automated, and everything works as intended.</p>
<p>Experiment succeeded.</p>
<p>Links:</p>
<ul>
<li><a href="https://wordpress.com/pricing/">https://wordpress.com/pricing/</a></li>
<li><a href="https://en.support.wordpress.com/custom-design/#frequently-asked-questions">https://en.support.wordpress.com/custom-design/#frequently-asked-questions</a></li>
<li><a href="https://jekyllrb.com/">https://jekyllrb.com/</a></li>
<li><a href="https://jamstack.org/">https://jamstack.org/</a></li>
<li><a href="https://github.com/automata/awesome-jamstack">https://github.com/automata/awesome-jamstack</a></li>
</ul>]]></content:encoded>
    </item>
  </channel>
</rss>