<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mounir's blog]]></title><description><![CDATA[Software Developer/Architect
Mostly working with Django/Python]]></description><link>https://blog.mounirmesselmeni.de</link><generator>RSS for Node</generator><lastBuildDate>Fri, 09 Oct 2026 16:43:18 GMT</lastBuildDate><atom:link href="https://blog.mounirmesselmeni.de/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Django-Syzygy: Safe Zero-Downtime Database Migrations]]></title><description><![CDATA[Introduction
Django-Syzygy is a Django companion that makes database migrations safer in real production environments. It focuses on one specific, painful problem: migrations that are technically valid, but unsafe during rolling deployments.
Syzygy w...]]></description><link>https://blog.mounirmesselmeni.de/django-syzygy-safe-zero-downtime-database-migrations</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/django-syzygy-safe-zero-downtime-database-migrations</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Fri, 16 Jan 2026 16:13:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/52jRtc2S_VE/upload/33cdb9461230fa61c6e2c4812ce4836a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Django-Syzygy is a Django companion that makes database migrations safer in real production environments. It focuses on one specific, painful problem: migrations that are technically valid, but unsafe during rolling deployments.</p>
<p>Syzygy works with Django’s existing migration system and workflow. It doesn’t replace it, and it doesn’t require you to learn a new mental model. It quietly adds structure where Django is intentionally flexible, so deployments stop being a source of stress.</p>
<h2 id="heading-what-makes-it-different">What Makes It Different?</h2>
<p>Consider a common change: removing a database field that’s no longer needed.</p>
<p>In development, this is trivial. In production, during a rolling deploy, it can break running instances that still expect that column to exist. Django doesn’t protect you from that class of failure.</p>
<p>Syzygy does.</p>
<p><strong>The natural workflow:</strong></p>
<ol>
<li><p>You change your model (for example, remove a field)</p>
</li>
<li><p>Run <code>makemigrations</code> as usual</p>
</li>
<li><p>Syzygy detects a potentially unsafe change and asks for minimal input</p>
</li>
<li><p>It automatically splits the change into safe deployment stages</p>
</li>
</ol>
<p>You keep using Django’s commands. Syzygy handles the staging.</p>
<h2 id="heading-why-django-syzygy-exists">Why Django-Syzygy Exists</h2>
<h3 id="heading-the-problem">The Problem</h3>
<p>Django migrations are excellent at describing <em>what</em> should change, but they don’t encode <em>when</em> a change is safe to apply relative to your application code.</p>
<p>A classic failure mode looks like this:</p>
<p><strong>Without Syzygy:</strong></p>
<ul>
<li><p>You remove a field from a model</p>
</li>
<li><p>A migration drops the column</p>
</li>
<li><p>You deploy new code</p>
</li>
<li><p>Old application instances are still running</p>
</li>
<li><p>Migration runs → database column disappears → runtime errors</p>
</li>
</ul>
<p>This is not a rare edge case. It’s a structural issue with rolling deployments.</p>
<p><strong>With Syzygy:</strong></p>
<ul>
<li><p>The migration is automatically split into deployment-safe stages</p>
</li>
<li><p>Changes that old code can tolerate run first</p>
</li>
<li><p>Code is deployed</p>
</li>
<li><p>Breaking changes run only after old code is gone</p>
</li>
</ul>
<p>The result is not “perfect safety,” but the elimination of an entire category of deployment-time failures.</p>
<h2 id="heading-what-syzygy-actually-does">What Syzygy Actually Does</h2>
<p>Syzygy integrates with Django’s migration autodetector and migration system:</p>
<ul>
<li><p>It analyzes generated migrations for deployment safety</p>
</li>
<li><p>It assigns or infers a <strong>stage</strong> (pre-deploy or post-deploy)</p>
</li>
<li><p>When necessary, it splits migrations automatically</p>
</li>
<li><p>It enforces consistency via Django system checks</p>
</li>
</ul>
<p>It does <strong>not</strong> replace Django’s migration engine or alter database behavior. All migrations are still standard Django migrations.</p>
<h2 id="heading-how-it-works-in-practice">How It Works in Practice</h2>
<h3 id="heading-removing-a-field">Removing a Field</h3>
<pre><code class="lang-python"><span class="hljs-comment"># models.py</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyModel</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">100</span>)
    <span class="hljs-comment"># old_field removed</span>
</code></pre>
<pre><code class="lang-bash">python manage.py makemigrations
</code></pre>
<p>Syzygy detects that removing a field is unsafe if old code is still running, and asks for a temporary default value so existing rows remain compatible.</p>
<p>It generates two migrations:</p>
<pre><code class="lang-python"><span class="hljs-comment"># 0001_pre_deploy.py</span>
operations = [
    syzygy.operations.AlterField(
        model_name=<span class="hljs-string">'mymodel'</span>,
        name=<span class="hljs-string">'old_field'</span>,
        field=models.CharField(default=<span class="hljs-string">'safe_default'</span>),
    ),
]
</code></pre>
<pre><code class="lang-python"><span class="hljs-comment"># 0002_remove_field.py</span>
operations = [
    migrations.RemoveField(
        model_name=<span class="hljs-string">'mymodel'</span>,
        name=<span class="hljs-string">'old_field'</span>,
    ),
]
</code></pre>
<p>The first migration keeps old application instances working.<br />The second removes the field only after deployment.</p>
<h3 id="heading-adding-a-field-without-a-default">Adding a Field Without a Default</h3>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyModel</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">100</span>)
    new_field = models.CharField(max_length=<span class="hljs-number">200</span>)
</code></pre>
<p>Syzygy ensures the database can accept writes from old code:</p>
<pre><code class="lang-python"><span class="hljs-comment"># 0001_add_field.py (pre-deploy)</span>
operations = [
    migrations.AddField(
        model_name=<span class="hljs-string">'mymodel'</span>,
        name=<span class="hljs-string">'new_field'</span>,
        field=models.CharField(
            max_length=<span class="hljs-number">200</span>,
            db_default=<span class="hljs-string">'temp_default'</span>,
        ),
    ),
]
</code></pre>
<pre><code class="lang-python"><span class="hljs-comment"># 0002_drop_db_default.py (post-deploy)</span>
operations = [
    syzygy.operations.AlterField(
        model_name=<span class="hljs-string">'mymodel'</span>,
        name=<span class="hljs-string">'new_field'</span>,
        field=models.CharField(max_length=<span class="hljs-number">200</span>),
        stage=syzygy.constants.Stage.POST_DEPLOY,
    ),
]
</code></pre>
<p>The database default exists only to bridge the deployment window. Syzygy removes it once the new code is fully live.</p>
<p><em>(PostgreSQL is the recommended database; support for other backends depends on their DDL capabilities.)</em></p>
<h2 id="heading-pre-deploy-vs-post-deploy">Pre-Deploy vs Post-Deploy</h2>
<p>Syzygy classifies operations based on one rule:</p>
<p><strong>Old code must survive pre-deploy changes.<br />New code must survive post-deploy changes.</strong></p>
<p>Typical classification:</p>
<p>Pre-Deploy:</p>
<ul>
<li><p>Adding columns or tables</p>
</li>
<li><p>Adding indexes</p>
</li>
<li><p>Adding database defaults</p>
</li>
<li><p>Creating new constraints</p>
</li>
</ul>
<p>Post-Deploy:</p>
<ul>
<li><p>Removing or renaming fields</p>
</li>
<li><p>Dropping constraints</p>
</li>
<li><p>Destructive schema changes</p>
</li>
</ul>
<p>If Syzygy can’t determine this safely, it fails early.</p>
<h2 id="heading-installation-amp-setup">Installation &amp; Setup</h2>
<pre><code class="lang-bash">pip install django-syzygy
</code></pre>
<pre><code class="lang-python">INSTALLED_APPS = [
    <span class="hljs-comment"># ...</span>
    <span class="hljs-string">'syzygy'</span>,
]
</code></pre>
<p>Optional configuration for edge cases:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> syzygy <span class="hljs-keyword">import</span> Stage

MIGRATION_THIRD_PARTY_STAGES_FALLBACK = Stage.PRE_DEPLOY

MIGRATION_STAGES_OVERRIDE = {
    <span class="hljs-string">'problematic_app.0001_bad_migration'</span>: Stage.POST_DEPLOY,
}
</code></pre>
<h2 id="heading-daily-workflow">Daily Workflow</h2>
<h3 id="heading-development">Development</h3>
<p>Nothing changes:</p>
<pre><code class="lang-bash">python manage.py makemigrations
python manage.py check
python manage.py migrate
</code></pre>
<h3 id="heading-production-deployment">Production Deployment</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Apply safe changes</span>
python manage.py migrate --pre-deploy

<span class="hljs-comment"># Deploy application code</span>

<span class="hljs-comment"># Apply remaining changes</span>
python manage.py migrate
</code></pre>
<p>If you forget <code>--pre-deploy</code>, <code>migrate</code> still works. The flag enables phased deployments; it doesn’t break existing pipelines.</p>
<h2 id="heading-cicd-integration">CI/CD Integration</h2>
<p>The most valuable part of Syzygy in CI is simple:</p>
<p><strong>Migration staging mistakes become CI failures instead of production incidents.</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">script:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">python</span> <span class="hljs-string">manage.py</span> <span class="hljs-string">check</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">python</span> <span class="hljs-string">manage.py</span> <span class="hljs-string">makemigrations</span> <span class="hljs-string">--dry-run</span> <span class="hljs-string">--check</span>
</code></pre>
<p>Syzygy’s system checks ensure migrations are internally consistent and safely staged.</p>
<h2 id="heading-when-syzygy-complains">When Syzygy Complains</h2>
<h3 id="heading-cannot-automatically-determine-stage">“Cannot automatically determine stage”</h3>
<p>This means a migration mixes safe and unsafe operations in a way that requires human intent.</p>
<p>You can:</p>
<ul>
<li><p>Split the migration</p>
</li>
<li><p>Explicitly assign a stage</p>
</li>
<li><p>Override it in settings</p>
</li>
</ul>
<p>The important part: the failure happens early, not during deployment.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<p>Syzygy doesn’t replace good judgment:</p>
<ul>
<li><p>Review generated migrations</p>
</li>
<li><p>Test migrations in staging</p>
</li>
<li><p>Prefer additive changes over destructive ones</p>
</li>
<li><p>Document complex transitions</p>
</li>
</ul>
<p>Syzygy enforces structure; it doesn’t remove responsibility.</p>
<h2 id="heading-who-this-is-for">Who This Is For</h2>
<p>Django-Syzygy is most useful for:</p>
<ul>
<li><p>Teams running rolling or blue-green deployments</p>
</li>
<li><p>Kubernetes/ ArgoCD / Docker environments</p>
</li>
<li><p>Applications with real uptime requirements</p>
</li>
</ul>
<p>It’s probably unnecessary for:</p>
<ul>
<li><p>Solo projects</p>
</li>
<li><p>Development-only environments</p>
</li>
<li><p>Apps that can afford downtime</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Django-Syzygy makes deployment-unsafe migrations explicit and enforceable. It doesn’t promise perfection. It removes an entire class of avoidable production failures by encoding deployment reality directly into Django migrations.</p>
<p>You keep Django’s workflow.<br />You gain deployment safety.<br />And migration anxiety becomes a lot rarer.</p>
<p>That’s the whole point.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Apache Kafka: A Beginner's Guide to Partitions, Consumers, and Migrations]]></title><description><![CDATA[Apache Kafka is a cornerstone of modern distributed systems, yet its fundamental concepts can be daunting for newcomers. This guide demystifies Kafka's core—partitions, consumers, and topic migrations—with clear explanations, practical examples, and ...]]></description><link>https://blog.mounirmesselmeni.de/understanding-apache-kafka-a-beginners-guide-to-partitions-consumers-and-migrations</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/understanding-apache-kafka-a-beginners-guide-to-partitions-consumers-and-migrations</guid><category><![CDATA[kafka]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Fri, 25 Jul 2025 18:29:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Ype9sdOPdYc/upload/af2bc61d4859958c609f9cbed5e0e312.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Apache Kafka is a cornerstone of modern distributed systems, yet its fundamental concepts can be daunting for newcomers. This guide demystifies Kafka's core—partitions, consumers, and topic migrations—with clear explanations, practical examples, and visual aids to set you on the right path.</p>
<h2 id="heading-what-is-apache-kafka">What is Apache Kafka?</h2>
<p>Apache Kafka is a <strong>distributed streaming platform</strong> built for high-throughput, fault-tolerant messaging. Imagine a massive, continuous conveyor belt: producers place messages onto it, and consumers retrieve them, each at their own pace.</p>
<h2 id="heading-core-concepts-topics-amp-partitions">Core Concepts: Topics &amp; Partitions</h2>
<h3 id="heading-topics-the-communication-channels">Topics: The Communication Channels</h3>
<p>In Kafka, a <strong>topic</strong> is a named channel or stream where producers publish messages and consumers subscribe to read them. Think of a topic as:</p>
<ul>
<li><strong>A database table:</strong> Each message is an immutable, append-only row.</li>
<li><strong>A persistent queue:</strong> Unlike traditional queues, Kafka topics are <strong>durable</strong> and <strong>replayable</strong>. Messages are not removed after being read, allowing multiple consumers to process the same data independently and at their own pace.</li>
</ul>
<p><strong>Examples:</strong> <code>user-signups</code>, <code>payment-transactions</code>, <code>order-updates</code></p>
<h3 id="heading-partitions-the-key-to-parallelism">Partitions: The Key to Parallelism</h3>
<p>Each Kafka topic is divided into <strong>partitions</strong>, which are ordered, immutable sequences of messages. Partitions are fundamental to Kafka's <strong>scalability</strong> and <strong>parallelism</strong>.</p>
<pre><code class="lang-mermaid">graph TD
    A[Topic: user-events] --&gt; B[Partition 0]
    A --&gt; C[Partition 1]
    A --&gt; D[Partition 2]

    B --&gt; B1[Message 1]
    B --&gt; B2[Message 2]
    B --&gt; B3[Message 3]

    C --&gt; C1[Message 1]
    C --&gt; C2[Message 2]

    D --&gt; D1[Message 1]
    D --&gt; D2[Message 2]
    D --&gt; D3[Message 3]
    D --&gt; D4[Message 4]
</code></pre>
<p>Every message within a partition has a unique, sequential <strong>offset</strong>. Kafka guarantees <strong>message order only within a single partition</strong>, not across the entire topic.</p>
<h3 id="heading-how-kafka-assigns-messages-to-partitions-the-message-key">How Kafka Assigns Messages to Partitions: The Message Key</h3>
<p>Producers can include an optional <strong>key</strong> with each message. This key determines the target partition using a consistent hashing strategy:</p>
<ul>
<li><strong>With a key:</strong> <code>partition = hash(key) % num_partitions</code></li>
<li><strong>Without a key:</strong> Kafka uses a round-robin approach or a custom partitioner.</li>
</ul>
<p>This mechanism ensures:</p>
<ul>
<li>All messages with the <strong>same key</strong> are directed to the <strong>same partition</strong>.</li>
<li>Message order is preserved <strong>only</strong> for messages sharing the same key.</li>
</ul>
<p><strong>Crucial Point:</strong> If strict message order is vital for related events (e.g., all actions by a specific user), always use the same key for those messages.</p>
<h4 id="heading-keyed-message-example">Keyed Message Example</h4>
<p>Consider processing <code>user-events</code> using <code>user_id</code> as the key:</p>
<pre><code class="lang-mermaid">graph TD
    A[Producer] --&gt;|user_id: 101| P0[Partition 0]
    A --&gt;|user_id: 102| P1[Partition 1]
    A --&gt;|user_id: 103| P2[Partition 2]
    A --&gt;|user_id: 101| P0
    A --&gt;|user_id: 102| P1
    A --&gt;|user_id: 103| P2
</code></pre>
<p>Here, all events for <code>user_id: 101</code> consistently land in Partition 0, <code>user_id: 102</code> in Partition 1, and so on. This preserves order for individual users while still achieving parallelism across different users.</p>
<p><strong>Design Tip:</strong> Use keys intentionally to group related events and plan your partition count based on expected key distribution.</p>
<h2 id="heading-consumers-and-consumer-groups">Consumers and Consumer Groups</h2>
<h3 id="heading-why-one-consumer-per-partition">Why One Consumer Per Partition?</h3>
<p>Kafka's design dictates that <strong>only one consumer within a consumer group can read from a given partition at any time</strong>. This strict rule is crucial for preserving <strong>message order</strong> within partitions.</p>
<p>If multiple consumers could read from the same partition concurrently, message ordering would break, especially for keyed messages where sequential processing is critical.</p>
<pre><code class="lang-mermaid">graph TD
    subgraph Partition 0
        M1[Message 1 - user 101]
        M2[Message 2 - user 101]
        M3[Message 3 - user 101]
    end

    subgraph Consumer A
        A1[Receives M1]
        A2[Receives M3]
    end

    subgraph Consumer B
        B1[Receives M2]
    end

    M1 --&gt; A1
    M2 --&gt; B1
    M3 --&gt; A2
</code></pre>
<p><strong>Note:</strong> The above diagram illustrates a <em>hypothetical scenario</em> that is <strong>impossible in Kafka</strong>. It demonstrates <em>why</em> Kafka enforces the one-consumer-per-partition rule: to prevent out-of-order processing of messages within a partition.</p>
<p>Kafka's design ensures:</p>
<ul>
<li>✅ One partition → One active consumer (per group)</li>
<li>✅ Guaranteed <strong>in-order delivery</strong> for messages within that partition</li>
</ul>
<h3 id="heading-what-is-a-consumer-group">What is a Consumer Group?</h3>
<p>A <strong>consumer group</strong> is a collection of consumers that cooperate to read data from a topic. Kafka distributes the topic's partitions among the consumers in the group, ensuring each partition is processed by exactly one consumer.</p>
<ul>
<li>Multiple consumer groups can read the same topic independently, each receiving a full copy of all messages.</li>
<li>Within a single consumer group, partitions are <strong>exclusively assigned</strong> to individual consumers.</li>
</ul>
<pre><code class="lang-mermaid">graph TD
    A[Topic: payments&lt;br/&gt;1 Partition] --&gt; B[Partition 0]

    subgraph Consumer Group
        C1[Consumer 1] --&gt; B
        C2[Consumer 2] -.-&gt; |Idle| B
        C3[Consumer 3] -.-&gt; |Idle| B
    end

    style C1 fill:#4CAF50,stroke:#388E3C
    style C2 fill:#9E9E9E,stroke:#616161
    style C3 fill:#9E9E9E,stroke:#616161
</code></pre>
<h3 id="heading-partition-count-vs-consumer-count-maximizing-parallelism">Partition Count vs. Consumer Count: Maximizing Parallelism</h3>
<p>To fully leverage consumer parallelism, you should aim for:</p>
<p><code>number_of_partitions &gt;= number_of_consumers_in_group</code></p>
<ul>
<li>If you have <strong>fewer partitions than consumers</strong>, some consumers will remain <strong>idle</strong> as partitions cannot be shared.</li>
<li>If you have <strong>more partitions than consumers</strong>, some consumers will process multiple partitions.</li>
</ul>
<p><strong>Why?</strong> Partitions are Kafka's unit of parallelism. More partitions allow for more parallel consumers, leading to higher throughput.</p>
<h3 id="heading-multiple-partitions-parallel-consumers">Multiple Partitions = Parallel Consumers</h3>
<p>Increasing the number of partitions directly enables <strong>parallel processing</strong>.</p>
<pre><code class="lang-mermaid">graph TD
    A[Topic: orders] --&gt; B[Partition 0]
    A --&gt; C[Partition 1]
    A --&gt; D[Partition 2]
    A --&gt; E[Partition 3]

    F[4 Partitions] --&gt; A

    subgraph Consumer Group
        C1[Consumer 1] --&gt; B
        C1 --&gt; C
        C2[Consumer 2] --&gt; D
        C2 --&gt; E
    end

    style C1 fill:#4CAF50
    style C2 fill:#4CAF50
</code></pre>
<p>This design allows Kafka to scale <strong>linearly</strong> with the number of partitions.</p>
<h3 id="heading-rebalancing-consumers">Rebalancing Consumers</h3>
<p>When consumers join or leave a group, Kafka automatically <strong>rebalances partition ownership</strong> among the remaining or new consumers.</p>
<pre><code class="lang-mermaid">sequenceDiagram
    participant T as Topic (3 partitions)
    participant C1 as Consumer 1
    participant C2 as Consumer 2
    participant C3 as Consumer 3
    participant C4 as Consumer 4

    T-&gt;&gt;C1: Assign Partition 0
    T-&gt;&gt;C2: Assign Partition 1
    T-&gt;&gt;C3: Assign Partition 2
    Note right of C4: Idle - no partitions left

    Note over C1,C3: Rebalancing occurs when consumers join/leave

    C1-&gt;&gt;T: Consumer 1 leaves
    T-&gt;&gt;C2: Assign Partition 0
    T-&gt;&gt;C4: Assign Partition 1
    T-&gt;&gt;C3: Keep Partition 2
</code></pre>
<p><strong>Impact:</strong> Rebalancing can cause temporary processing delays (lag spikes) as partitions are reassigned.</p>
<h2 id="heading-scaling-with-partitions-a-critical-consideration">Scaling with Partitions: A Critical Consideration</h2>
<h3 id="heading-you-can-only-increase-partitions">You Can Only <strong>Increase</strong> Partitions</h3>
<p>Kafka allows you to increase the number of partitions for a topic, but <strong>never decrease them</strong>. This limitation exists due to:</p>
<ol>
<li><strong>Offset Mismatch Risks:</strong> Decreasing partitions could lead to inconsistencies in message offsets.</li>
<li><strong>Potential Data Loss:</strong> Messages might be lost during a partition reduction.</li>
<li><strong>Consumer Confusion:</strong> Rebalancing becomes chaotic and unreliable.</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-comment"># ✅ Increase partitions</span>
kafka-topics --alter --topic user-events --partitions 8 \
  --bootstrap-server localhost:9092

<span class="hljs-comment"># ❌ This will fail</span>
<span class="hljs-comment"># kafka-topics --alter --topic user-events --partitions 2</span>
</code></pre>
<h3 id="heading-planning-your-partition-count-start-small-scale-up">Planning Your Partition Count: Start Small, Scale Up</h3>
<p>It's generally best to <strong>start with a lower number of partitions</strong> and increase them as your needs grow. Starting with too many can introduce unnecessary overhead. Since you can only increase, not decrease, beginning small allows for smoother, iterative scaling.</p>
<p>Monitor throughput and consumer utilization to determine the optimal time to add more partitions. Also consider:</p>
<ul>
<li><strong>Expected throughput:</strong> How much data will flow through this topic?</li>
<li><strong>Number of consumers:</strong> How many consumers will process this data?</li>
<li><strong>Future growth:</strong> Anticipate your system's scaling needs.</li>
</ul>
<h2 id="heading-kafka-topic-migrations-navigating-the-challenges">Kafka Topic Migrations: Navigating the Challenges</h2>
<p>Moving Kafka topics between clusters or environments presents unique challenges.</p>
<h3 id="heading-the-unreadable-message-problem">The “Unreadable Message” Problem</h3>
<p>This common issue often arises with Avro-based data:</p>
<pre><code class="lang-mermaid">graph LR
    A[Source Cluster] --&gt; B[Messages with&lt;br/&gt;Schema ID 42]
    B --&gt; C[Migration Tool]
    C --&gt; D[Target Cluster]
    D --&gt; E[Messages with&lt;br/&gt;Schema ID 42]
    E -.-&gt; F[Schema Registry&lt;br/&gt;ID 42 ≠ Different Schema!]
</code></pre>
<p>The problem occurs when the message's embedded schema ID in the new environment points to a <em>different</em> schema than intended, rendering messages unreadable.</p>
<h3 id="heading-tombstone-messages-dont-lose-them">Tombstone Messages: Don't Lose Them!</h3>
<p>Tombstone messages are special messages with <code>null</code> values used for:</p>
<ul>
<li>Deleting keys in <strong>compacted topics</strong>.</li>
<li>Triggering data cleanup.</li>
<li>Representing logical deletions.</li>
</ul>
<p>Ensure these critical messages are handled correctly during migration to avoid data inconsistencies.</p>
<h2 id="heading-best-practices-for-topic-migration">Best Practices for Topic Migration</h2>
<ol>
<li><strong>Test thoroughly:</strong> Always test migrations with sample data first.</li>
<li><strong>Schema Registry compatibility:</strong> Verify that schema registries in source and target environments are compatible.</li>
<li><strong>Tombstone handling:</strong> Confirm your migration strategy correctly handles tombstone messages.</li>
<li><strong>Monitor rebalancing:</strong> Keep a close eye on consumer group rebalancing during and after migration.</li>
<li><strong>Tool caution:</strong> Use tools like MirrorMaker2 or Confluent Replicator with a deep understanding of their behavior and limitations.</li>
</ol>
<h2 id="heading-practical-cli-examples">Practical CLI Examples</h2>
<h3 id="heading-scenario-1-ordered-low-volume-events">Scenario 1: Ordered, Low-Volume Events</h3>
<p>For topics requiring strict ordering and handling low throughput (e.g., user actions where sequence is critical):</p>
<pre><code class="lang-bash">kafka-topics --create --topic user-actions \
  --partitions 1 --replication-factor 3 \
  --bootstrap-server localhost:9092
</code></pre>
<p><strong>Outcome:</strong> Only one consumer will be active, ensuring strict ordering.</p>
<h3 id="heading-scenario-2-high-throughput-event-streams">Scenario 2: High Throughput Event Streams</h3>
<p>For topics demanding high parallelism and real-time processing (e.g., web events for analytics):</p>
<pre><code class="lang-bash">kafka-topics --create --topic web-events \
  --partitions 16 --replication-factor 3 \
  --bootstrap-server localhost:9092
</code></pre>
<p><strong>Outcome:</strong> Up to 16 consumers can process messages in parallel, ideal for real-time analytics or event sourcing.</p>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><strong>Partitions = Parallelism:</strong> The fundamental unit for scaling Kafka.</li>
<li><strong>One Partition → One Consumer (per group):</strong> Ensures in-order processing within a partition.</li>
<li><strong>Partition Count is Immutable Downward:</strong> Plan carefully; you can only increase partitions.</li>
<li><strong>Migration is Tricky:</strong> Pay close attention to schema compatibility and tombstone messages.</li>
<li><strong>Rebalance = Temporary Lag Spikes:</strong> Be aware of processing delays during consumer group rebalancing.</li>
</ul>
<p>Understanding Kafka’s <strong>partition model</strong> and <strong>consumer mechanics</strong> is paramount for building <strong>resilient, scalable</strong> distributed systems. As the saying goes, "With great partitioning comes great responsibility." 🧠</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>This guide provided a solid foundation in Kafka's core concepts. However, Kafka is a vast ecosystem with many advanced features, including:</p>
<ul>
<li>Kafka Streams for real-time stream processing</li>
<li>Exactly-once semantics and transactions for data integrity</li>
<li>Schema Registry and various serialization formats</li>
<li>Comprehensive security, monitoring, and operational tooling</li>
</ul>
<p>Consider this your starting point. Continue exploring Kafka's broader capabilities to unlock its full potential in your distributed applications!</p>
]]></content:encoded></item><item><title><![CDATA[Zero Down Time DB migrations with Django]]></title><description><![CDATA[Avoiding Downtime in Django Migrations with django-migration-linter
Deploying Django apps with zero downtime is essential in modern production systems, especially when rolling out updates on Kubernetes or deploying worker-based systems incrementally....]]></description><link>https://blog.mounirmesselmeni.de/zero-down-time-db-migrations-with-django</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/zero-down-time-db-migrations-with-django</guid><category><![CDATA[Django]]></category><category><![CDATA[database migrations]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Wed, 02 Jul 2025 16:27:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/pFjMjuZpm24/upload/0b70f0c2bd2b77d7ac481652c092d624.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-avoiding-downtime-in-django-migrations-with-django-migration-linter">Avoiding Downtime in Django Migrations with <code>django-migration-linter</code></h1>
<p>Deploying Django apps with zero downtime is essential in modern production systems, especially when rolling out updates on Kubernetes or deploying worker-based systems incrementally. However, even a simple migration, like adding a new field, can silently disrupt your system if not handled carefully.</p>
<p>In this post, we’ll explore how <code>django-migration-linter</code> helps detect backward-incompatible migrations <strong>before</strong> they reach production—and how to effectively integrate it into your Django project and CI pipeline.</p>
<hr />
<h2 id="heading-migrations-are-more-dangerous-than-they-look">🚨 Migrations Are More Dangerous Than They Look</h2>
<p>Django makes database migrations easy—but that simplicity can be deceptive. Let’s say you add a new field like this:</p>
<pre><code class="lang-python"><span class="hljs-comment"># models.py</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Order</span>(<span class="hljs-params">models.Model</span>):</span>
    status = models.CharField(max_length=<span class="hljs-number">50</span>, default=<span class="hljs-string">"draft"</span>)
</code></pre>
<p>You run <code>makemigrations</code>, commit, and deploy. Everything seems fine, right?</p>
<p>Wrong.</p>
<p>Django's <code>default=</code> only applies <strong>at the Python level</strong>, not in the database itself. If your application uses old code that tries to write to this model without populating the new field, and there's no database-level default (<code>db_default</code>), your code can crash with a <code>NOT NULL constraint failed</code>.</p>
<p>This becomes especially dangerous during rolling deployments where both the <strong>old code and new schema</strong> run simultaneously.</p>
<p><strong>Scenario:</strong></p>
<ul>
<li>Migration is applied, a new column is created without a DB default  </li>
<li>A user tries to create a new row via the backend (still running old code)  </li>
<li>The backend doesn’t know about the new field and doesn’t send it  </li>
<li>The DB rejects the insertion → crash</li>
</ul>
<hr />
<h2 id="heading-common-pitfalls-in-zero-downtime-migrations">⚠️ Common Pitfalls in Zero-Downtime Migrations</h2>
<p>Here are a few common operations that seem innocent but can cause serious trouble during deployments:</p>
<ul>
<li>❌ Adding a <code>NOT NULL</code> field without a <code>db_default</code></li>
<li>❌ Renaming a field or table (breaks old code)</li>
<li>❌ Dropping a field still used by old workers</li>
<li>❌ Changing field types or constraints directly</li>
</ul>
<p>To see a full list of potentially incompatible operations, check out this fantastic <a target="_blank" href="https://github.com/3YOURMIND/django-migration-linter/blob/main/docs/incompatibilities.md">incompatibility reference</a>.</p>
<hr />
<h2 id="heading-what-is-django-migration-linter">🔧 What is <code>django-migration-linter</code>?</h2>
<p><a target="_blank" href="https://github.com/3YOURMIND/django-migration-linter"><code>django-migration-linter</code></a> is a Django app that scans your migrations and flags operations that are <strong>not backward compatible</strong>. It acts as a safety net to ensure that every migration you write won’t break live systems when old and new code run side-by-side.</p>
<h3 id="heading-what-it-catches">✅ What it catches:</h3>
<ul>
<li>Dangerous field additions</li>
<li>Dropped fields or indexes</li>
<li>Renames and type changes</li>
<li>Constraint modifications</li>
</ul>
<p>It’s especially useful in CI pipelines and rolling deployments.</p>
<hr />
<h2 id="heading-installation">📦 Installation</h2>
<p>Install <code>django-migration-linter</code> as part of your project dependencies (not just in CI):</p>
<pre><code class="lang-bash">pip install django-migration-linter
</code></pre>
<p>Then, add it to your <code>INSTALLED_APPS</code> in <code>settings.py</code>:</p>
<pre><code class="lang-python">INSTALLED_APPS += [<span class="hljs-string">"django_migration_linter"</span>]
</code></pre>
<h3 id="heading-optional-configuration">Optional Configuration</h3>
<p>You can further customize the linter in <code>settings.py</code>:</p>
<pre><code class="lang-python">MIGRATION_LINTER_OPTIONS = {
    <span class="hljs-comment"># Only check migrations added since this commit (optional for legacy projects)</span>
    <span class="hljs-string">"git_commit_id"</span>: <span class="hljs-string">"SOMECOMMITHASH"</span>,
    <span class="hljs-string">"project_root_path"</span>: BASE_DIR,
}
</code></pre>
<hr />
<h2 id="heading-linting-migrations-in-your-project">🧪 Linting Migrations in Your Project</h2>
<p>Run the migration linter using Django's management command:</p>
<pre><code class="lang-bash">python manage.py lintmigrations
</code></pre>
<p>This will lint all migrations unless you configure it to only check those added since a specific Git commit (useful in legacy codebases).</p>
<hr />
<h2 id="heading-why-this-matters-in-kubernetes-and-rolling-deployments">🚀 Why This Matters in Kubernetes and Rolling Deployments</h2>
<p>Let’s visualize a common deployment pattern:</p>
<pre><code class="lang-mermaid">sequenceDiagram
    participant Dev as Developer
    participant CI as CI/CD
    participant K8s as Kubernetes
    participant DB as Database

    Dev-&gt;&gt;CI: Push code with migration
    CI-&gt;&gt;DB: Apply migration
    CI-&gt;&gt;K8s: Start rolling update
    K8s-&gt;&gt;App: Some pods use old code
    K8s-&gt;&gt;App: Some pods use new code
    App-&gt;&gt;DB: Old code reads/writes old schema
    App-&gt;&gt;DB: New code reads/writes new schema
    Note over DB: Must support both code versions
</code></pre>
<p>Of course, this is a simplified view (not involving GitOps), but our main goal is to demonstrate the possible issues.</p>
<p>Also note the time gap between the migration being applied and the start of the deployment. Even without rolling updates, your system may run old code against the new schema for some time. This is why every schema change must be <strong>backward compatible</strong>—<code>django-migration-linter</code> helps enforce that rule.</p>
<hr />
<h2 id="heading-ci-integration-github-actions-and-gitlab-ci">⚙️ CI Integration: GitHub Actions and GitLab CI</h2>
<p>You can block unsafe migrations in your CI by running the linter after installing your Django app.</p>
<h3 id="heading-github-actions">✅ GitHub Actions</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">Django</span> <span class="hljs-string">Migrations</span>

<span class="hljs-attr">on:</span> [<span class="hljs-string">pull_request</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">lint-migrations:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">python-version:</span> <span class="hljs-number">3.13</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          pip install -r requirements.txt
</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Migration</span> <span class="hljs-string">Linter</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
          <span class="hljs-string">python</span> <span class="hljs-string">manage.py</span> <span class="hljs-string">lintmigrations</span> <span class="hljs-string">--no-cache</span>
</code></pre>
<h3 id="heading-gitlab-ci">✅ GitLab CI</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">lint_migrations:</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">python:3.13</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">lint</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">-r</span> <span class="hljs-string">requirements.txt</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">python</span> <span class="hljs-string">manage.py</span> <span class="hljs-string">lintmigrations</span> <span class="hljs-string">--no-cache</span>
  <span class="hljs-attr">interruptible:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>This ensures that only <strong>new</strong> migrations are linted on pull requests or merge requests. You can also configure your pipeline to skip the job if no migration files were added or updated.</p>
<hr />
<h2 id="heading-resources">📚 Resources</h2>
<ul>
<li>🔗 GitHub Repo: <a target="_blank" href="https://github.com/3YOURMIND/django-migration-linter">3YOURMIND/django-migration-linter</a></li>
<li>📖 Incompatibility List: <a target="_blank" href="https://github.com/3YOURMIND/django-migration-linter/blob/main/docs/incompatibilities.md">See all unsafe patterns</a></li>
</ul>
<hr />
<h2 id="heading-conclusion">🔚 Conclusion</h2>
<p>Schema changes are deceptively simple in Django, but their impact can be critical in a live environment. <code>django-migration-linter</code> helps you enforce safety by detecting migration operations that aren’t backward compatible—<strong>before you deploy</strong>.</p>
<p>✅ Add it to your project<br />✅ Run it locally<br />✅ Enforce it in CI</p>
<p><em>Your users won’t even notice you migrated.</em></p>
<hr />
<p>🧩 <em>Have you used</em> <code>django-migration-linter</code> <em>in production? Share your thoughts or lessons learned in the comments!</em></p>
]]></content:encoded></item><item><title><![CDATA[🏗️ Building Beautiful, Responsive Django Forms with Tailwind and Widget Tweaks]]></title><description><![CDATA[Building beautiful, responsive forms in Django doesn’t have to be a painful experience. By combining the power of django-tailwind with django-widget-tweaks and a few well-crafted custom CSS classes, you can create stunning, consistent forms that look...]]></description><link>https://blog.mounirmesselmeni.de/building-beautiful-responsive-django-forms-with-tailwind-and-widget-tweaks</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/building-beautiful-responsive-django-forms-with-tailwind-and-widget-tweaks</guid><category><![CDATA[Django]]></category><category><![CDATA[django forms]]></category><category><![CDATA[Tailwind CSS]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sun, 15 Jun 2025 19:50:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/kmF_Aq8gkp0/upload/e80926284219af41976fda9ff0069389.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building beautiful, responsive forms in Django doesn’t have to be a painful experience. By combining the power of <a target="_blank" href="https://github.com/timonweb/django-tailwind"><strong>django-tailwind</strong></a> with <a target="_blank" href="https://github.com/jazzband/django-widget-tweaks"><strong>django-widget-tweaks</strong></a> and a few well-crafted custom CSS classes, you can create stunning, consistent forms that look great across all devices <strong>without cluttering your templates with endless utility classes</strong>.</p>
<p>In this guide, you’ll learn how to set up this powerful combination and build reusable, maintainable form components.</p>
<hr />
<h2 id="heading-why-this-combination-works">✅ Why This Combination Works</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/timonweb/django-tailwind"><strong>django-tailwind</strong></a> Integrates <a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a> into your Django project with hot reloading and easy configuration.</p>
</li>
<li><p><a target="_blank" href="https://github.com/jazzband/django-widget-tweaks"><strong>django-widget-tweaks</strong></a> Lets you dynamically add classes and attributes to form fields right in your templates — no need to modify the form definitions themselves.</p>
</li>
<li><p><strong>Custom CSS Classes</strong> You can group frequently-used Tailwind utilities into semantic, reusable class names, simplifying your markup and promoting a consistent design system.</p>
</li>
</ul>
<hr />
<h2 id="heading-setting-up">⚙️ Setting Up</h2>
<h3 id="heading-1-install-dependencies">1️⃣ Install dependencies</h3>
<pre><code class="lang-bash">pip install django-tailwind django-widget-tweaks
</code></pre>
<h3 id="heading-2-update-your-django-settings">2️⃣ Update your Django settings</h3>
<p>In <code>settings.py</code>:</p>
<pre><code class="lang-python">INSTALLED_APPS = [
    <span class="hljs-comment"># your other apps...</span>
    <span class="hljs-string">'tailwind'</span>,
    <span class="hljs-string">'theme'</span>,  <span class="hljs-comment"># this will be your Tailwind app</span>
    <span class="hljs-string">'widget_tweaks'</span>,
]

TAILWIND_APP_NAME = <span class="hljs-string">'theme'</span>
</code></pre>
<h3 id="heading-3-initialize-tailwind">3️⃣ Initialize Tailwind</h3>
<pre><code class="lang-bash">python manage.py tailwind init
</code></pre>
<p>This will create a <code>theme</code> app containing Tailwind’s configuration and build pipeline.</p>
<hr />
<h2 id="heading-creating-custom-css-classes">🎨 Creating Custom CSS Classes</h2>
<p>Inside <code>theme/static_src/src/styles.css</code>, define <strong>semantic</strong> classes that bundle complex Tailwind utilities. For example:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss"</span>;

<span class="hljs-comment">/* Dark mode variant helper */</span>
<span class="hljs-keyword">@custom-variant</span> dark (&amp;:where(.dark, .dark *));

<span class="hljs-comment">/* Example: Form Fields */</span>
<span class="hljs-selector-class">.form-field-element</span> {
  @apply block w-full p-2 border border-gray-300 <span class="hljs-attribute">dark</span>:border-gray-<span class="hljs-number">700</span>
         rounded-md focus:ring-blue-<span class="hljs-number">500</span> focus:border-blue-<span class="hljs-number">500</span>
         dark:focus:ring-blue-<span class="hljs-number">400</span> dark:focus:border-blue-<span class="hljs-number">400</span>
         dark:bg-gray-<span class="hljs-number">800</span> dark:text-gray-<span class="hljs-number">300</span>;
}

<span class="hljs-selector-class">.form-field-label</span> {
  @apply block text-sm font-medium text-gray-700 <span class="hljs-attribute">dark</span>:text-gray-<span class="hljs-number">300</span>;
}
</code></pre>
<hr />
<h2 id="heading-note-on-using-apply">⚠️ Note on Using <code>@apply</code></h2>
<p>Tailwind’s <a target="_blank" href="https://tailwindcss.com/docs/functions-and-directives#apply-directive"><code>@apply</code> directive</a> is very handy for grouping repetitive utility classes into a single, meaningful class name. However, it’s <strong>not a replacement for Tailwind’s utility-first philosophy</strong>.</p>
<p><strong>Best practice:</strong> ✅ Use <code>@apply</code> for <strong>logical, reusable patterns</strong> — like form elements, cards, or buttons that appear consistently throughout your project. 🚫 Avoid creating a custom class for every possible utility combo — this defeats Tailwind’s purpose and can cause CSS bloat and specificity headaches.</p>
<p>In short: <strong>be intentional</strong> — use custom classes to encapsulate genuine design patterns, not every tiny variation.</p>
<hr />
<h2 id="heading-reusable-form-components">🧩 Reusable Form Components</h2>
<h3 id="heading-example-basic-field-component">Example: Basic field component</h3>
<p><code>templates/components/forms/field.html</code></p>
<pre><code class="lang-html">{% load widget_tweaks %}

{% if field.is_hidden %}
  {{ field }}
{% else %}
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-6"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"{{ field.id_for_label }}"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-field-label"</span>&gt;</span>
      {{ field.label }}
      {% if field.field.required %}<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-red-500 ml-1"</span>&gt;</span>*<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>{% endif %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

    {% if field.errors %}
      {% render_field field class="form-field-element border-red-300 dark:border-red-600 focus:border-red-500 focus:ring-red-200" placeholder=field.label %}
      {% for error in field.errors %}
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-2 text-sm text-red-600 dark:text-red-400"</span>&gt;</span>{{ error }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      {% endfor %}
    {% else %}
      {% render_field field class="form-field-element" placeholder=field.label %}
    {% endif %}

    {% if field.help_text %}
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-2 text-sm text-slate-500 dark:text-slate-400"</span>&gt;</span>{{ field.help_text|safe }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    {% endif %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endif %}
</code></pre>
<hr />
<h2 id="heading-key-benefits">✅ Key Benefits</h2>
<h3 id="heading-1-semantic-names">✔️ 1. Semantic Names</h3>
<p>Create meaningful class names like <code>.form-field-element</code> instead of repeating long utility strings.</p>
<h3 id="heading-2-consistent-design">✔️ 2. Consistent Design</h3>
<p>Reusable styles guarantee your forms look cohesive everywhere.</p>
<h3 id="heading-3-built-in-dark-mode">✔️ 3. Built-in Dark Mode</h3>
<p>Dark mode support works out of the box by leveraging Tailwind’s <code>dark:</code> variant.</p>
<h3 id="heading-4-easy-maintenance">✔️ 4. Easy Maintenance</h3>
<p>Update your design system in a single CSS file — no messy search-and-replace in templates.</p>
<h3 id="heading-5-fully-responsive">✔️ 5. Fully Responsive</h3>
<p>Tailwind utilities and custom breakpoints ensure your forms adapt beautifully to all screen sizes.</p>
<hr />
<h2 id="heading-development-workflow">🚀 Development Workflow</h2>
<ul>
<li><p><strong>Start Tailwind’s build process during development:</strong></p>
<pre><code class="lang-bash">  python manage.py tailwind start
</code></pre>
</li>
<li><p><strong>Build a minified version for production:</strong></p>
<pre><code class="lang-bash">  python manage.py tailwind build
</code></pre>
</li>
</ul>
<hr />
<h2 id="heading-recommended-template-structure">📁 Recommended Template Structure</h2>
<pre><code class="lang-plaintext">templates/
├── base.html
├── components/
│   └── forms/
│       ├── field.html
│       ├── password_field.html
│       └── checkbox_field.html
└── your_app/
    └── form.html
</code></pre>
<hr />
<h2 id="heading-best-practices">💡 Best Practices</h2>
<p>✅ Use semantic, purposeful custom classes for true design patterns</p>
<p>✅ Keep related styles together in your Tailwind CSS build</p>
<p>✅ Use Tailwind’s <code>@apply</code> wisely — don’t recreate utility classes unnecessarily</p>
<p>✅ Always test your forms in both light and dark modes</p>
<p>✅ DRY: reuse components instead of duplicating markup</p>
<hr />
<h2 id="heading-conclusion">🎉 Conclusion</h2>
<p>Combining <a target="_blank" href="https://github.com/timonweb/django-tailwind"><strong>django-tailwind</strong></a>, <a target="_blank" href="https://github.com/jazzband/django-widget-tweaks"><strong>django-widget-tweaks</strong></a>, and a few well-chosen custom CSS classes is a powerful, maintainable way to build beautiful Django forms.</p>
<p>Start small, build a library of reusable components, and enjoy the flexibility and power of Tailwind’s utility-first design — without sacrificing clean, readable templates.</p>
<p>✨ <strong>Happy coding — and beautiful forms ahead!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Leveraging HTMX for Dynamic UI in Django Projects]]></title><description><![CDATA[What is HTMX?
HTMX is a powerful tool that allows developers to create highly interactive and dynamic user interfaces without the need for heavy JavaScript frameworks. At its core, HTMX uses HTML attributes to define behaviour, making it an ideal com...]]></description><link>https://blog.mounirmesselmeni.de/leveraging-htmx-for-dynamic-ui-in-django-projects</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/leveraging-htmx-for-dynamic-ui-in-django-projects</guid><category><![CDATA[Django]]></category><category><![CDATA[htmx]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Wed, 07 Feb 2024 20:50:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xxeAftHHq6E/upload/2bad0b03e3354f5e659d89d50115e259.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-htmx">What is HTMX?</h2>
<p>HTMX is a powerful tool that allows developers to create highly interactive and dynamic user interfaces without the need for heavy JavaScript frameworks. At its core, HTMX uses HTML attributes to define behaviour, making it an ideal companion to traditional server-side frameworks such as Django.</p>
<h3 id="heading-key-features-of-htmx">Key features of HTMX:</h3>
<p>Simplicity: HTMX simplifies the process of adding dynamic functionality to web applications by using familiar HTML attributes such as hx-get, hx-post, and hx-trigger.</p>
<p>Minimal JavaScript: Unlike complex JavaScript frameworks, HTMX requires minimal JavaScript code, reducing the overhead associated with client-side scripting.</p>
<p>Server-side rendering: HTMX integrates seamlessly with server-side rendering frameworks such as Django, allowing developers to leverage the power of both front-end and back-end technologies.</p>
<p>Progressive enhancement: HTMX follows the principle of progressive enhancement, ensuring that basic functionality is available to all users, while improving the experience for those with JavaScript-enabled browsers.</p>
<p>Compatibility: HTMX is compatible with all modern browsers and works well with existing web technologies, making it easy to integrate into existing projects.</p>
<h2 id="heading-how-htmx-works-with-django">How HTMX works with Django:</h2>
<p>In a Django application, HTMX can be used to create dynamic UI elements within Django templates, enabling real-time updates without reloading pages. By using Django's powerful template language alongside HTMX attributes, developers can easily create dynamic and responsive web applications.</p>
<h3 id="heading-benefits-of-using-htmx-with-django-include">Benefits of using HTMX with Django include</h3>
<ul>
<li><p>Simplified development: HTMX simplifies the development process by allowing developers to focus on writing HTML and Python code rather than complex JavaScript logic.</p>
</li>
<li><p>Improved performance: With its minimal JavaScript footprint, HTMX helps improve the performance of Django applications by reducing the amount of client-side processing required.</p>
</li>
<li><p>Improved user experience: By enabling real-time updates and interactions, HTMX improves the overall user experience, resulting in higher user engagement and satisfaction.</p>
</li>
</ul>
<p>In the next section, we'll dive into the process of setting up a Django project with HTMX, using Poetry for dependency management.</p>
<h2 id="heading-setting-up-the-project">Setting up the Project</h2>
<p>No interest in reading step by step guide, <a target="_blank" href="https://github.com/mounirmesselmeni/htmxango">here</a> is the full example in Github.</p>
<p>Poetry is a dependency management tool for Python that simplifies the process of managing project dependencies and virtual environments. In this section, we'll guide you through setting up a new Django project with Poetry and installing the necessary dependencies for integrating HTMX.</p>
<p><strong>1. Install Poetry:</strong> First, make sure you have Poetry installed on your system. You can install Poetry using the following command:</p>
<pre><code class="lang-bash">curl -sSL https://install.python-poetry.org | python3 -
</code></pre>
<p>For alternative installation methods, refer to the Poetry documentation.</p>
<p><strong>2. Create a New Folder:</strong> Create a new folder for your Django project. You can do this using your system's file explorer or the command line:</p>
<pre><code class="lang-bash">mkdir my_django_project
<span class="hljs-built_in">cd</span> my_django_project
</code></pre>
<p><strong>3. Initialize Poetry:</strong> Initiate a new Poetry project within your project folder by running the following command:</p>
<pre><code class="lang-bash">poetry init
</code></pre>
<p>Follow the interactive prompt to provide details about your project, such as its name, version, and dependencies.</p>
<p><strong>4. Install Dependencies:</strong> Once the Poetry project is initialized, you can start installing the required dependencies. Run the following command to install the core dependencies:</p>
<pre><code class="lang-bash">poetry add django django-htmx django-crispy-forms crispy-bootstrap5 django-render-block
</code></pre>
<p>This command will add Django, django-htmx, django-crispy-forms, crispy-bootstrap5, and django-render-block to your project's dependencies.</p>
<p><strong>5. Add Development Dependencies:</strong> Additionally, you may want to add development dependencies for tasks such as code formatting, linting, and debugging. You can install these dependencies using the following command:</p>
<pre><code class="lang-bash">poetry add black ruff djlint ipdb ipython --group dev
</code></pre>
<p>This command will add black, ruff, djlint, ipdb, and ipython as development dependencies.</p>
<h2 id="heading-setting-up-the-django-project">Setting up the Django Project</h2>
<p>Now that we have installed the necessary dependencies with Poetry, let's proceed to set up the Django project. In this section, we'll guide you through initializing a new Django project and configuring it to integrate with HTMX.</p>
<p><strong>1. Initialize Django Project:</strong> Run the following command to initialize a new Django project in the current directory:</p>
<pre><code class="lang-bash">django-admin startproject django_htmx .
</code></pre>
<p>This command creates a new Django project named <code>django_htmx</code> in the current directory. The dot (<code>.</code>) at the end specifies that the project should be created in the current directory.</p>
<p><strong>2. Configure Settings:</strong> Navigate to the <code>django_htmx</code> directory and open the <a target="_blank" href="http://settings.py"><code>settings.py</code></a> file within your favorite text editor. Update the <code>INSTALLED_APPS</code> setting to include the installed dependencies:</p>
<pre><code class="lang-python">INSTALLED_APPS = [
    ...
    <span class="hljs-string">'django_htmx'</span>,
    <span class="hljs-string">'crispy_forms'</span>,
    <span class="hljs-string">'crispy_bootstrap5'</span>,
    <span class="hljs-string">'django_render_block'</span>,
    <span class="hljs-string">'htmx'</span>,
]
</code></pre>
<p>Make sure to replace <code>'app_name'</code> with the name of your Django app.</p>
<p><strong>3. Configure Middleware:</strong> In the same <a target="_blank" href="http://settings.py"><code>settings.py</code></a> file, add the HTMX middleware to the <code>MIDDLEWARE</code> setting:</p>
<pre><code class="lang-python">MIDDLEWARE = [
    ...
    <span class="hljs-string">'htmx.middleware.HtmxMiddleware'</span>,
]
</code></pre>
<p>This middleware is essential for HTMX to intercept and process AJAX requests.</p>
<p>Django-htmx adds extra parameters to the request based on the recognised headers, making it easy for us to use them later in our views. E.g. the request.htmx which we can use to know if the request is coming from htmx or not.</p>
<p><strong>4. Configure Crispy Forms:</strong> If you plan to use Crispy Forms for styling your forms, add the following settings to <a target="_blank" href="http://settings.py"><code>settings.py</code></a>:</p>
<pre><code class="lang-python">CRISPY_ALLOWED_TEMPLATE_PACKS = <span class="hljs-string">'bootstrap5'</span>
CRISPY_TEMPLATE_PACK = <span class="hljs-string">'bootstrap5'</span>
</code></pre>
<p>This configures Crispy Forms to use Bootstrap 5 for styling.</p>
<p><strong>5. Run Migrations:</strong> Before you can start using your Django project, you need to apply the initial database migrations. Run the following command:</p>
<pre><code class="lang-bash">python manage.py migrate
</code></pre>
<p>This command creates the necessary database tables for your Django project.</p>
<p><strong>6. Verify Setup:</strong> To verify that the project setup is successful, you can start the Django development server by running:</p>
<pre><code class="lang-bash">python manage.py runserver
</code></pre>
<p>Open a web browser and navigate to <a target="_blank" href="http://127.0.0.1:8000"><code>http://127.0.0.1:8000</code></a>. If you see the Django welcome page, congratulations! Your Django project is set up and ready to integrate with HTMX.</p>
<p>Great! Let's set up the <code>todos</code> app with the provided model, URLs, and admin configurations.</p>
<h3 id="heading-todos-app">Todos app</h3>
<p><strong>1. Create the</strong><code>todos</code><strong>App:</strong> If you haven't already created the <code>todos</code> app, you can create it using the following command:</p>
<pre><code class="lang-python">python manage.py startapp todos
</code></pre>
<p>This command will create a new app named <code>todos</code>.</p>
<p><strong>2. Define the Model:</strong> Inside the <a target="_blank" href="http://models.py"><code>models.py</code></a> file of the <code>todos</code> app, define the <code>TodoItem</code> model as provided:</p>
<pre><code class="lang-python"><span class="hljs-comment"># todos/models.py</span>

<span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoItem</span>(<span class="hljs-params">models.Model</span>):</span>
    title = models.CharField(max_length=<span class="hljs-number">100</span>)
    completed_on = models.DateTimeField(null=<span class="hljs-literal">True</span>, blank=<span class="hljs-literal">True</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        ordering = [<span class="hljs-string">"-id"</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.title
</code></pre>
<p><strong>3. Define the URLs:</strong> Create a <a target="_blank" href="http://urls.py"><code>urls.py</code></a> file inside the <code>todos</code> app directory and define the URLs as provided:</p>
<pre><code class="lang-python"><span class="hljs-comment"># todos/urls.py</span>

<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

app_name = <span class="hljs-string">"todos"</span>

urlpatterns = [
    path(<span class="hljs-string">""</span>, views.TodoItemListView.as_view(), name=<span class="hljs-string">"todoitem-list"</span>),
    path(<span class="hljs-string">"create/"</span>, views.TodoItemCreateView.as_view(), name=<span class="hljs-string">"todoitem-create"</span>),
    path(<span class="hljs-string">"&lt;int:pk&gt;/mark-complete/"</span>, views.TodoMarkComplete.as_view(), name=<span class="hljs-string">"todoitem-mark-complete"</span>),
    path(<span class="hljs-string">"&lt;int:pk&gt;/delete/"</span>, views.TodoItemDeleteView.as_view(), name=<span class="hljs-string">"todoitem-delete"</span>),
]
</code></pre>
<p><strong>4. Create Views (to be implemented):</strong> You'll need to create the views mentioned in the URLs (<a target="_blank" href="http://views.py"><code>views.py</code></a>) for <code>TodoItemListView</code>, <code>TodoItemCreateView</code>, <code>TodoMarkComplete</code>, and <code>TodoItemDeleteView</code>.</p>
<p><strong>5. Register Model with Admin:</strong> In the <a target="_blank" href="http://admin.py"><code>admin.py</code></a> file of the <code>todos</code> app, register the <code>TodoItem</code> model as provided:</p>
<pre><code class="lang-python"><span class="hljs-comment"># todos/admin.py</span>

<span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> TodoItem

<span class="hljs-meta">@admin.register(TodoItem)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoItemAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    list_display = (<span class="hljs-string">"title"</span>, <span class="hljs-string">"completed_on"</span>)
    search_fields = (<span class="hljs-string">"title"</span>,)
</code></pre>
<p><strong>6. Configure Settings:</strong> Make sure the <code>todos</code> app is included in the <code>INSTALLED_APPS</code> list in your project's <a target="_blank" href="http://settings.py"><code>settings.py</code></a> file.</p>
<h3 id="heading-integrating-htmx-with-class-based-views-in-django">Integrating HTMX with Class-Based Views in Django</h3>
<p>In this section, we'll explore how to integrate HTMX with Django's Class-Based Views (CBVs) using a custom <code>HtmxTemplateResponseMixin</code>. We'll examine the provided <a target="_blank" href="http://views.py"><code>views.py</code></a> file, which contains CBVs for handling todo items in our Django application, and explain how each view contributes to creating dynamic user interfaces with HTMX.</p>
<pre><code class="lang-python"><span class="hljs-comment"># todos/views.py</span>

<span class="hljs-comment"># Import necessary modules and classes</span>
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Any
<span class="hljs-keyword">from</span> django <span class="hljs-keyword">import</span> forms
<span class="hljs-keyword">from</span> django.http <span class="hljs-keyword">import</span> HttpResponse
<span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> redirect
<span class="hljs-keyword">from</span> django.template.response <span class="hljs-keyword">import</span> TemplateResponse
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> reverse_lazy
<span class="hljs-keyword">from</span> django.utils <span class="hljs-keyword">import</span> timezone
<span class="hljs-keyword">from</span> django.views.generic <span class="hljs-keyword">import</span> CreateView, DeleteView, ListView, View
<span class="hljs-keyword">from</span> django_htmx.http <span class="hljs-keyword">import</span> retarget
<span class="hljs-keyword">from</span> render_block <span class="hljs-keyword">import</span> render_block_to_string
<span class="hljs-keyword">from</span> todos.forms <span class="hljs-keyword">import</span> TodoItemForm
<span class="hljs-keyword">from</span> todos.models <span class="hljs-keyword">import</span> TodoItem

<span class="hljs-comment"># Define a custom TemplateResponse class for HTMX integration</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HtmxResponseClass</span>(<span class="hljs-params">TemplateResponse</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, htmx_template_block: str | None = None, *args, **kwargs</span>):</span>
        self.htmx_template_block = htmx_template_block
        super().__init__(*args, **kwargs)

<span class="hljs-meta">    @property</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">rendered_content</span>(<span class="hljs-params">self</span>):</span>
        context = self.resolve_context(self.context_data)
        <span class="hljs-keyword">if</span> self._request.htmx <span class="hljs-keyword">and</span> self.htmx_template_block:
            <span class="hljs-keyword">return</span> render_block_to_string(
                self.template_name,
                block_name=self.htmx_template_block,
                context=self.context_data,
                request=self._request,
            )
        template = self.resolve_template(self.template_name)
        <span class="hljs-keyword">return</span> template.render(context, self._request)

<span class="hljs-comment"># Define a mixin for HTMX template responses</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HtmxTemplateResponseMixin</span>:</span>
    response_class = HtmxResponseClass
    htmx_template_block = <span class="hljs-literal">None</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">render_to_response</span>(<span class="hljs-params">self, context, **response_kwargs</span>):</span>
        response_kwargs.setdefault(<span class="hljs-string">"content_type"</span>, self.content_type)
        <span class="hljs-keyword">return</span> self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            htmx_template_block=self.htmx_template_block,
            **response_kwargs,
        )

<span class="hljs-comment"># Define a view for listing todo items</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoItemListView</span>(<span class="hljs-params">HtmxTemplateResponseMixin, ListView</span>):</span>
    model = TodoItem
    template_name = <span class="hljs-string">"todos/todoitem_list.html"</span>
    context_object_name = <span class="hljs-string">"todoitems"</span>
    ordering = [<span class="hljs-string">"-id"</span>]
    htmx_template_block = <span class="hljs-string">"object_list_block"</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_context_data</span>(<span class="hljs-params">self, **kwargs: Any</span>) -&gt; dict[str, Any]:</span>
        context = super().get_context_data(**kwargs)
        context[<span class="hljs-string">"form"</span>] = TodoItemForm()
        <span class="hljs-keyword">return</span> context

<span class="hljs-comment"># Define a view for creating todo items</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoItemCreateView</span>(<span class="hljs-params">HtmxTemplateResponseMixin, CreateView</span>):</span>
    form_class = TodoItemForm
    template_name = <span class="hljs-string">"todos/todoitem_list.html"</span>
    htmx_template_block = <span class="hljs-string">"form_block"</span>
    http_method_names = [<span class="hljs-string">"post"</span>]
    success_url = reverse_lazy(<span class="hljs-string">"todos:todoitem-list"</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">form_invalid</span>(<span class="hljs-params">self, form: forms.ModelForm</span>) -&gt; HttpResponse:</span>
        response = super().form_invalid(form)
        <span class="hljs-keyword">return</span> retarget(response, <span class="hljs-string">"this"</span>)

<span class="hljs-comment"># Define a view for deleting todo items</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoItemDeleteView</span>(<span class="hljs-params">DeleteView</span>):</span>
    model = TodoItem
    success_url = reverse_lazy(<span class="hljs-string">"todos:todoitem-list"</span>)
    http_method_names = [<span class="hljs-string">"post"</span>]

<span class="hljs-comment"># Define a view for marking todo items as complete</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoMarkComplete</span>(<span class="hljs-params">View</span>):</span>
    http_method_names = [<span class="hljs-string">"post"</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">post</span>(<span class="hljs-params">self, request, *args, **kwargs</span>):</span>
        todoitem = TodoItem.objects.get(pk=kwargs[<span class="hljs-string">"pk"</span>])
        todoitem.completed_on = timezone.now()
        todoitem.save()
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"todos:todoitem-list"</span>)
</code></pre>
<p><strong>Understanding the Views</strong></p>
<p>Now, let's break down what each view does:</p>
<ol>
<li><p><strong>TodoItemListView</strong>: This view lists all todo items in the database. It utilizes the <code>HtmxTemplateResponseMixin</code> to handle HTMX requests, so that we return the whole content when accessing the page via the browser and only the list when accessing it via HTMX requests.</p>
<p> The <code>get_context_data</code> method adds a form (<code>TodoItemForm</code>) to the context to enable the creation of new todo items.</p>
</li>
<li><p><strong>TodoItemCreateView</strong>: This view handles the creation of new todo items. It uses the <code>HtmxTemplateResponseMixin</code> to handle HTMX requests and overrides the <code>form_invalid</code> method to return an HTMX response with validation errors.</p>
</li>
<li><p><strong>TodoItemDeleteView</strong>: This view deletes todo items from the database. It specifies the model (<code>TodoItem</code>) and the success URL to redirect to after deletion.</p>
</li>
<li><p><strong>TodoMarkComplete</strong>: This view marks a todo item as complete by updating its <code>completed_on</code> field with the current timestamp. It redirects the user to the todo item list view (<code>TodoItemListView</code>) after marking the item as complete.</p>
</li>
</ol>
<p>By using CBVs with the <code>HtmxTemplateResponseMixin</code>, we can seamlessly integrate HTMX functionality into our Django application, providing users with a dynamic and interactive user experience.</p>
<h3 id="heading-django-templates">Django Templates</h3>
<p>In this section, we'll define a base HTML template (<code>base.html</code>) that serves as the foundation for our HTMX-powered Django application. This template provides the necessary structure and includes links to Bootstrap for styling and HTMX for seamless AJAX interactions.</p>
<p><strong>1. Add Template Directory to Settings:</strong> Before creating the base template, ensure that the <code>templates</code> directory is configured in your Django project's <a target="_blank" href="http://settings.py"><code>settings.py</code></a> file:</p>
<pre><code class="lang-python"><span class="hljs-comment"># settings.py</span>

TEMPLATES = [
    {
        ...
        <span class="hljs-string">'DIRS'</span>: [BASE_DIR / <span class="hljs-string">"templates"</span>],
        ...
    },
]
</code></pre>
<p>This configuration specifies the location of the templates directory relative to the project's base directory.</p>
<p><strong>2. Define Base Template (</strong><code>base.html</code><strong>):</strong></p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- templates/base.html --&gt;</span>
{% load static %}
{% load django_htmx %}

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span> <span class="hljs-attr">data-bs-theme</span>=<span class="hljs-string">"dark"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
              <span class="hljs-attr">content</span>=<span class="hljs-string">"HTMXANGO is a Django application that demonstrates how to use HTMX with Django."</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"keywords"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"HTMXANGO, HTMX, Django, Python"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>
            {% block title %}
                HTMXANGO
            {% endblock title %}
        <span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"</span>
              <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
              <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"</span>
              <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
              <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"</span>&gt;</span>
        {% block styles %}
        {% endblock styles %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">hx-headers</span>=<span class="hljs-string">'{"X-CSRFToken": "{{ csrf_token }}"}'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
            {% block content %}
            {% endblock content %}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        {% block scripts %}
        {% endblock scripts %}
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"</span>
                <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"</span>
                <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/htmx.org@1.9.10"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
        {% django_htmx_script %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p><strong>Understanding the Base Template:</strong></p>
<ul>
<li><p><strong>Load Static Files</strong>: The <code>{% load static %}</code> tag allows us to reference static files such as CSS and JavaScript within our templates.</p>
</li>
<li><p><strong>Load Django-HTMX Tags</strong>: The <code>{% load django_htmx %}</code> tag enables the usage of HTMX-specific template tags and attributes.</p>
</li>
<li><p><strong>HTML Structure</strong>: The template follows standard HTML5 structure with <code>&lt;head&gt;</code> and <code>&lt;body&gt;</code> sections.</p>
</li>
<li><p><strong>Title Block</strong>: The <code>{% block title %}</code> tag allows child templates to define their own titles while providing a default title ("HTMX Django").</p>
</li>
<li><p><strong>Bootstrap Integration</strong>: The template includes links to Bootstrap CSS and JavaScript files for styling and interactivity.</p>
</li>
<li><p><strong>CSRF Token</strong>: The <code>hx-headers</code> attribute ensures that HTMX requests include the CSRF token for security purposes.</p>
</li>
<li><p><strong>Content Block</strong>: The <code>{% block content %}</code> tag defines a placeholder for child templates to inject their content.</p>
</li>
<li><p><strong>Scripts Block</strong>: The <code>{% block scripts %}</code> tag defines a placeholder for additional JavaScript scripts.</p>
</li>
<li><p><strong>HTMX Integration</strong>: The template includes HTMX JavaScript library (<code>htmx.min.js</code>) and the <code>{% django_htmx_script %}</code> tag for HTMX-specific configurations django-htmx third party app.</p>
<h3 id="heading-understanding-the-use-of-csrf-token-in-htmx-requests">Understanding the Use of CSRF Token in HTMX Requests</h3>
<p>  In the base HTML template (<code>base.html</code>) provided for our HTMX-powered Django application, you may have noticed the inclusion of the <code>hx-headers</code> attribute with the CSRF token in the <code>&lt;body&gt;</code> tag:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">hx-headers</span>=<span class="hljs-string">'{"X-CSRFToken": "{{ csrf_token }}"}'</span>&gt;</span>
</code></pre>
<p>  This addition serves a crucial security purpose when making HTMX requests within our application.</p>
<p>  <strong>Why include a CSRF token in HTMX requests?</strong></p>
<p>  Django protects against Cross-Site Request Forgery (CSRF) attacks by requiring the inclusion of a CSRF token in requests that change state (e.g. POST requests). This token is typically included in forms as a hidden field and is validated by Django to ensure that the request comes from the same site and is not forged by a malicious third party.</p>
<p>  If we are using HTMX to make AJAX requests within our application, we need to ensure that these requests include the CSRF token so that Django can validate them properly. Including the CSRF token in HTMX requests helps prevent CSRF attacks and ensures the security of our application.</p>
<p>  <strong>How it works</strong></p>
<p>  By including the <code>hx-headers</code> attribute with the CSRF token in the tag, we instruct HTMX to include this token in all AJAX requests made within our application. This ensures that each request contains the necessary CSRF token for Django to validate.</p>
</li>
</ul>
<h3 id="heading-implementing-todo-list-template-with-htmx">Implementing Todo List Template with HTMX</h3>
<p>In this section, we'll define the <code>todoitem_list.html</code> template, which is responsible for displaying the list of todo items in our HTMX-powered Django application. This template extends the base template (<code>base.html</code>) and includes HTMX-specific attributes and functionality for dynamic interactions.</p>
<p><strong>1. Define Todo List Template (</strong><code>todoitem_list.html</code><strong>):</strong></p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- templates/todos/todoitem_list.html --&gt;</span>

{% extends "base.html" %}
{% load crispy_forms_tags %}

{% block content %}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row align-items-center"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Todo Item<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"spinner-border htmx-indicator"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"status"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"spinner"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sr-only"</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            {% block form_block %}
                <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>
                      <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"{% url 'todos:todoitem-create' %}"</span>
                      <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"innerHTML"</span>
                      <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"submit"</span>
                      <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"#todo-list"</span>
                      <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">"#spinner"</span>&gt;</span>
                    {% crispy form %}
                    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Save<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
            {% endblock form_block %}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Todo List<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"todo-list"</span>&gt;</span>
                {% block object_list_block %}
                    {% if todoitems %}
                        <span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table table-striped"</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">scope</span>=<span class="hljs-string">"col"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-1"</span>&gt;</span>#<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">scope</span>=<span class="hljs-string">"col"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-5"</span>&gt;</span>Title<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">scope</span>=<span class="hljs-string">"col"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-6"</span>&gt;</span>Completed<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
                                <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
                                {% for todo in todoitems %}
                                    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">scope</span>=<span class="hljs-string">"row"</span>&gt;</span>{{ todo.id }}<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ todo.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
                                            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-check-input"</span>
                                                   <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span>
                                                   <span class="hljs-attr">value</span>=<span class="hljs-string">""</span>
                                                   <span class="hljs-attr">id</span>=<span class="hljs-string">"completed-checkbox"</span>
                                                   {% <span class="hljs-attr">if</span> <span class="hljs-attr">todo.completed_on</span> %}<span class="hljs-attr">checked</span>{% <span class="hljs-attr">endif</span> %}
                                                   <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"{% url 'todos:todoitem-mark-complete' todo.id %}"</span>
                                                   <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>
                                                   <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"closest table"</span>
                                                   <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"click"</span>
                                                   {% <span class="hljs-attr">if</span> <span class="hljs-attr">todo.completed_on</span> %}<span class="hljs-attr">disabled</span>{% <span class="hljs-attr">endif</span> %}&gt;</span>
                                            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-check-label"</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"completed-checkbox"</span>&gt;</span>
                                                {% if todo.completed_on %}
                                                    {{ todo.completed_on }}
                                                {% else %}
                                                    Not Completed
                                                {% endif %}
                                            <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
                                            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"{% url 'todos:todoitem-delete' todo.id %}"</span>
                                                    <span class="hljs-attr">hx-confirm</span>=<span class="hljs-string">"Are you sure?"</span>
                                                    <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>
                                                    <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"closest table"</span>
                                                    <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"click"</span>
                                                    <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-danger btn-sm float-end"</span>
                                                    <span class="hljs-attr">tooltip</span>=<span class="hljs-string">"Delete Todo Item"</span>&gt;</span>
                                                <span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bi bi-trash"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span>
                                            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                                        <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
                                {% endfor %}
                            <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
                    {% else %}
                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No todo items found.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                    {% endif %}
                {% endblock object_list_block %}
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endblock content %}
</code></pre>
<p><strong>Understanding the Todo List Template:</strong></p>
<ul>
<li><p><strong>Extending Base Template</strong>: The template extends the <code>base.html</code> template to inherit its structure and functionality.</p>
</li>
<li><p><strong>Load Crispy Forms Tags</strong>: The <code>{% load crispy_forms_tags %}</code> tag enables the usage of Crispy Forms for rendering forms in a Bootstrap-friendly manner.</p>
</li>
<li><p><strong>Content Block</strong>: The <code>{% block content %}</code> tag defines the main content area of the template.</p>
</li>
<li><p><strong>Todo Item Form</strong>: The form block includes a form for creating new todo items. The form is submitted via HTMX with AJAX, and the response replaces the content of the todo list (<code>#todo-list</code>).</p>
</li>
<li><p><strong>Todo List Display</strong>: The <code>object_list_block</code> block iterates over todo items and displays them in a table format. Each todo item includes options to mark as complete or delete using HTMX-powered interactions.</p>
</li>
<li><p><strong>HTMX Attributes</strong>: HTMX attributes (<code>hx-post</code>, <code>hx-swap</code>, <code>hx-target</code>, <code>hx-trigger</code>, <code>hx-indicator</code>, <code>hx-confirm</code>) are used to define AJAX behavior, such as posting data, swapping HTML content, and indicating loading state.</p>
</li>
</ul>
<p>In this section, we've defined the <code>todoitem_list.html</code> template to display the list of todo items in our HTMX-powered Django application. This template uses HTMX attributes and functionality to enable dynamic interactions such as creating, marking as complete, and deleting todo items, all without refreshing the page.</p>
<p><strong>Testing HTMX Views in Django</strong></p>
<p>We'll write the test cases <code>test_views.py</code> to ensure the functionality of our HTMX-powered views in the Django application. These test cases cover various scenarios, including rendering templates, handling form submissions, and processing AJAX requests.</p>
<p><strong>1. Define Test Cases (</strong><code>test_views.py</code><strong>):</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># todos/tests/test_views.py</span>

<span class="hljs-keyword">from</span> django.test <span class="hljs-keyword">import</span> TestCase
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> reverse
<span class="hljs-keyword">from</span> todos.forms <span class="hljs-keyword">import</span> TodoItemForm
<span class="hljs-keyword">from</span> todos.models <span class="hljs-keyword">import</span> TodoItem

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoItemListViewTests</span>(<span class="hljs-params">TestCase</span>):</span>
    <span class="hljs-comment"># Test case to check rendering of the todo item list view</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_get</span>(<span class="hljs-params">self</span>):</span>
        response = self.client.get(reverse(<span class="hljs-string">"todos:todoitem-list"</span>))
        self.assertEqual(response.status_code, <span class="hljs-number">200</span>)
        self.assertIn(<span class="hljs-string">"form"</span>, response.context_data)
        self.assertIsInstance(response.context_data[<span class="hljs-string">"form"</span>], TodoItemForm)

    <span class="hljs-comment"># Test case to check rendering of the todo item list view with HTMX</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_get_with_htmx</span>(<span class="hljs-params">self</span>):</span>
        response = self.client.get(
            reverse(<span class="hljs-string">"todos:todoitem-list"</span>), headers={<span class="hljs-string">"hx-request"</span>: <span class="hljs-string">"true"</span>}
        )
        self.assertEqual(response.status_code, <span class="hljs-number">200</span>)
        self.assertContains(response, <span class="hljs-string">"No todo items found."</span>)
        self.assertNotContains(response, <span class="hljs-string">"form"</span>)

<span class="hljs-comment"># Additional test cases for other views (TodoItemCreateView, TodoItemDeleteView, TodoMarkCompleteTests) are omitted for brevity.</span>
</code></pre>
<p><strong>Understanding the Test Cases:</strong></p>
<ul>
<li><p><strong>TestCase Class Structure</strong>: Each test case class inherits from <code>django.test.TestCase</code>, providing access to Django's testing utilities.</p>
</li>
<li><p><strong>TodoItemListViewTests</strong>: This class contains test cases for the todo item list view (<code>TodoItemListView</code>).</p>
<ul>
<li><p><code>test_get</code>: Tests the rendering of the todo item list view and ensures that the form is present in the context.</p>
</li>
<li><p><code>test_get_with_htmx</code>: Tests the rendering of the todo item list view with HTMX enabled and verifies that the form is not included in the response.</p>
</li>
</ul>
</li>
<li><p><strong>Additional Test Cases</strong>: Similar test cases are defined for other views (<code>TodoItemCreateView</code>, <code>TodoItemDeleteView</code>, <code>TodoMarkCompleteTests</code>) to cover their respective functionalities. (check the github link at the end for the full example)</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we've looked at integrating HTMX with Django to create dynamic and interactive web applications. HTMX provides a lightweight and intuitive way to add AJAX functionality to Django projects, enabling seamless user experiences without the need for complex JavaScript frameworks.</p>
<p>We started by setting up a Django project with the necessary dependencies and configuring the HTMX middleware. We then defined models, views, templates and tests for a todo application, showing how HTMX simplifies the implementation of dynamic features such as form submissions, real-time updates and interactive user interfaces.</p>
<p>Through examples such as the todo item list view and form submissions, we demonstrated the power and flexibility of HTMX in extending Django applications. With HTMX, developers can leverage the simplicity of Django's templates and views while adding rich client-side interactions.</p>
<p>Testing HTMX views ensures the reliability and correctness of our application, allowing us to deploy features into production with confidence. By writing comprehensive test cases, we can verify that our HTMX-powered views behave as expected under various conditions.</p>
<p>To further explore and experiment with the codebase discussed in this article, you can access the full example on <a target="_blank" href="https://github.com/mounirmesselmeni/htmxango/">GitHub</a>. Feel free to fork, clone, and contribute to the project, and unlock the full potential of HTMX in your Django applications.</p>
<p>With HTMX, developers can deliver modern, responsive, and user-friendly web applications while leveraging the simplicity and robustness of the Django framework. Whether you're building a todo app, a dashboard, or an e-commerce platform, HTMX enables you to create delightful experiences for your users.</p>
<p>Harness the power of HTMX and take your Django projects to new heights of interactivity and engagement. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Streamline Your Software Updates with Renovate]]></title><description><![CDATA[In today's fast-paced technology landscape, keeping your software up to date is essential. Whether it's updating your backend applications or your frontend application software updates can improve performance, enhance security, and provide new featur...]]></description><link>https://blog.mounirmesselmeni.de/streamline-your-software-updates-with-renovate</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/streamline-your-software-updates-with-renovate</guid><category><![CDATA[packages]]></category><category><![CDATA[renovate]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[projects]]></category><category><![CDATA[repository]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Mon, 06 Mar 2023 22:49:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1678142845926/d103558e-b1d8-43ea-b1d0-d045b7b60784.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's fast-paced technology landscape, keeping your software up to date is essential. Whether it's updating your backend applications or your frontend application software updates can improve performance, enhance security, and provide new features.</p>
<p>However, manually managing software updates can be a tedious and time-consuming task, especially when you have to deal with multiple packages and dependencies.</p>
<p>This is where <a target="_blank" href="https://docs.renovatebot.com/">Renovate</a> comes in - an automated software update tool that simplifies the process of managing software updates and helps developers stay up-to-date with the latest releases.</p>
<p><a target="_blank" href="https://automatetheboringstuff.com/">Automating boring stuff</a> like package updates can help developers save time and reduce the risk of errors caused by manual intervention. By using automated update tools, developers can focus on more important tasks like writing code and improving functionality.</p>
<p><a target="_blank" href="https://xkcd.com/1319/"><img src="https://imgs.xkcd.com/comics/automation.png" alt="Automation" class="image--center mx-auto" /></a></p>
<h3 id="heading-gitlab-use-case">Gitlab use case</h3>
<p>Unfortunately, GitLab users have been left out in the cold when it comes to dependable automation tools like Dependabot, which is only available on GitHub. There are some libraries built on top of dependabot-core but the release cycle doesn't seem reliable. This is where Renovate comes in as a great alternative.</p>
<h3 id="heading-support-for-many-packages-managers-and-tools">Support for many packages managers and tools</h3>
<p>Renovate is a reliable and feature-packed tool that automates software updates across multiple package managers, including npm, Docker, and Git submodules. It supports multiple languages and tools like pre-commit, Docker, and Docker Compose, and is highly configurable to suit your specific needs. The tool is designed to be highly customizable, allowing you to configure updates for specific packages, control update frequency, and apply custom version ranges.</p>
<h3 id="heading-community-and-support">Community and support</h3>
<p>One of the best things about Renovate is the frequency of its releases. The tool is updated weekly with new features and bug fixes, ensuring that it stays up-to-date with the latest software releases. Additionally, the tool has an active community of developers who contribute to its development and support, providing a reliable and responsive support system for users.</p>
<h2 id="heading-running-renovate-from-a-docker-image">Running Renovate from a Docker image</h2>
<p>Renovate also offers a Docker image, making it easy to run as a container in your GitLab environment. By running Renovate as a Docker image, you can automatically create merge requests in GitLab, reducing the manual work required to manage software updates. The tool can be configured to run on a schedule or triggered by changes to your codebase, ensuring that your software is always up-to-date.</p>
<h3 id="heading-gitlab-token-setup">Gitlab Token setup</h3>
<p>To use Renovate with GitLab, you'll need to create a GitLab group token with developer access that you can use to authenticate with Renovate. Here's how you can create a GitLab group token with developer access:</p>
<ol>
<li><p>Log in to your GitLab account and navigate to the group or sub-group that you want to use with Renovate.</p>
</li>
<li><p>Click on the Settings button in the sidebar menu, and then click on Access Tokens under the Access Control section.</p>
</li>
<li><p>Click on the Create a token button to create a new token.</p>
</li>
<li><p>Enter a name and expiration date for the token, and then select the scopes that you want to grant the token. In this case, you'll want to grant the token the "API", "read_repository", and "write_repository" scopes.</p>
</li>
<li><p>Make sure that the "Developer" role is selected for the token's "Role" field.</p>
</li>
<li><p>Click on the Create personal access token button to create the token.</p>
</li>
<li><p>Copy the token that GitLab generates and store it somewhere secure, as you won't be able to access it again once you leave the page.</p>
</li>
</ol>
<p>Now that you have created a GitLab group token with the necessary scopes and roles, you can use it with Renovate to automate software updates in your GitLab environment. Remember to set the token as an environment variable called <code>RENOVATE_TOKEN</code> when running Renovate as a Docker container, as described in the previous answer.</p>
<h3 id="heading-github-token-yes-also-when-using-gitlab">GitHub token // Yes also when using Gitlab</h3>
<p>Renovate supports fetching release notes from GitHub for some package managers and languages, such as Node.js and Python. To fetch release notes, Renovate requires a GitHub personal access token with the <code>read_only</code> scope, which allows access to public repositories.</p>
<p>Here's how you can set up a GitHub token to be used only for fetching release notes:</p>
<ol>
<li><p>Log in to your GitHub account and navigate to the "Settings" page.</p>
</li>
<li><p>Click on "Developer settings" in the sidebar menu, and then click on "Personal access tokens."</p>
</li>
<li><p>Click on the "Generate new token" button to create a new token.</p>
</li>
<li><p>Enter a name for the token in the "Token description" field.</p>
</li>
<li><p>Under the "Select scopes" section, click on the "repo" dropdown menu and select "public_repo."</p>
</li>
<li><p>Click on the "Generate token" button to create the token.</p>
</li>
<li><p>Copy the token that GitHub generates and store it somewhere secure, as you won't be able to access it again once you leave the page.</p>
</li>
</ol>
<p>Now that you have created a GitHub token with the <code>public_repo</code> scope, you can use it with Renovate to fetch release notes for packages. To use the token with Renovate, you'll need to set it as an environment variable called <code>GITHUB_TOKEN</code> when running Renovate as a Docker container or via other deployment methods.</p>
<h3 id="heading-running-with-docker">Running with Docker</h3>
<pre><code class="lang-bash">docker run -it --rm \
  -e RENOVATE_TOKEN=<span class="hljs-variable">${RENOVATE_TOKEN}</span> \
  -e GITHUB_COM_TOKEN=<span class="hljs-variable">${GITHUB_TOKEN}</span>
  -e RENOVATE_REPOSITORIES=mygitlabgroup/gitlab-repo
  -e RENOVATE_PLATFORM=gitlab \
  renovate/renovate:latest
</code></pre>
<p>In the first run, Renovate will create an onboarding merge request which will create <code>renovate.json</code> config file.</p>
<p>For details about the possible configuration check out the <a target="_blank" href="https://docs.renovatebot.com/self-hosted-configuration/">documentation</a>.</p>
<p>An example of <code>renovate.json</code></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"https://docs.renovatebot.com/renovate-schema.json"</span>,
    <span class="hljs-attr">"labels"</span>: [
        <span class="hljs-string">"dependencies"</span>,
        <span class="hljs-string">"renovate"</span>
    ],
    <span class="hljs-attr">"separateMajorMinor"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"pre-commit"</span>: {
        <span class="hljs-attr">"enabled"</span>: <span class="hljs-literal">true</span>
    }
}
</code></pre>
<p>You can customize Renovate with such config, for example, to create a single MR for <a target="_blank" href="https://docs.renovatebot.com/noise-reduction/#package-grouping">dev dependencies</a>, to <a target="_blank" href="https://docs.renovatebot.com/presets-default/#automergeminor">auto-merge</a> specific changes, to exclude some packages by default or to assign a specific person.</p>
<p>To run Renovate for all repositories to which the GitLab token has access, you can run this instead of the previous command:</p>
<pre><code class="lang-bash">docker run -it --rm \
  -e RENOVATE_TOKEN=<span class="hljs-variable">${RENOVATE_TOKEN}</span> \
  -e GITHUB_COM_TOKEN=<span class="hljs-variable">${GITHUB_TOKEN}</span>
  -e RENOVATE_AUTODISCOVER=<span class="hljs-literal">true</span>
  -e RENOVATE_PLATFORM=gitlab \
  renovate/renovate:latest
</code></pre>
<p>You can run such a script in a cronjob or set it up properly in your <a target="_blank" href="https://docs.renovatebot.com/examples/self-hosting/#kubernetes">Kubernetes cluster</a>.</p>
<h3 id="heading-worth-mentioning">Worth mentioning</h3>
<p>The full version of the Renovate Docker image is typically easier to use than the slim version, as it includes support for Node and uses a sidecar to provide other necessary components. You can find more information on this topic in the Running <a target="_blank" href="https://docs.renovatebot.com/getting-started/running/#docker-image">Renovate section of the official documentation</a>.</p>
<p>If you have many projects and expect to Renovate to make a large number of calls, it's important to be aware of Dockerhub's rate limit, as this could impact your Dockerhub usage.</p>
<p>In general, running Renovate every week should be sufficient for most use cases.</p>
<p>If you have multiple outdated repositories, it may be easier to update them manually before starting any experimentation with Renovate.</p>
<p><img src="https://media.npr.org/assets/img/2023/01/14/this-is-fine_custom-dcb93e90c4e1548ffb16978a5a8d182270c872a9-s1100-c50.jpg" alt /></p>
<p>This can help prevent an overwhelming number of Merge requests from being created, which can be difficult to manage and review. Maybe start each project type at a time until you find the best configuration (React app, Python Backend, Java application etc...)</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, Renovate is an excellent tool for automating software updates and keeping your software up-to-date with the latest releases.</p>
<p>Its support for multiple package managers, languages, and tools, along with its frequent updates and active community, make it a reliable and flexible solution for developers. By using Renovate, you can save time and reduce the risk of errors caused by manual intervention, ensuring that your software remains performant, secure, and up-to-date.</p>
]]></content:encoded></item><item><title><![CDATA[Goodbye Flake8, Hello Ruff]]></title><description><![CDATA[Static code analysis is an essential part of ensuring the quality and maintainability of your Python code. There are many tools available for static code analysis, but Ruff stands out for its fast performance, comprehensive analysis, and ease of use....]]></description><link>https://blog.mounirmesselmeni.de/goodbye-flake8-hello-ruff</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/goodbye-flake8-hello-ruff</guid><category><![CDATA[Python]]></category><category><![CDATA[static code analysis]]></category><category><![CDATA[flake8]]></category><category><![CDATA[ruff]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Fri, 10 Feb 2023 19:09:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Skf7HxARcoc/upload/f6e418ae7e8f761b67c6fcfb3f9f4a50.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Static code analysis is an essential part of ensuring the quality and maintainability of your Python code. There are many tools available for static code analysis, but Ruff stands out for its fast performance, comprehensive analysis, and ease of use.</p>
<h2 id="heading-what-is-ruff">What is Ruff?</h2>
<p><a target="_blank" href="https://beta.ruff.rs/docs/">Ruff</a> is a static code analysis tool for Python that provides a fast and comprehensive analysis of your code. It checks your code against a set of predefined rules and best practices, helping you to identify and fix problems early in the development cycle. Ruff has a large number of built-in rules covering a wide range of coding standards and best practices, making it a comprehensive tool for ensuring the quality of your code.</p>
<h2 id="heading-advantages-of-ruff">Advantages of Ruff</h2>
<p>Fast performance: Ruff is written in Rust, a programming language renowned for its performance and efficiency. This makes Ruff much faster than other code analysis tools such as Flake8. Using Rust allows Ruff to quickly analyze even the largest Python projects, giving you fast and accurate results.</p>
<p>Comprehensive analysis: Ruff provides a comprehensive analysis of your code, covering a wide range of best practices and coding standards. This makes it easier to identify and fix issues early in the development cycle before they become bigger problems.</p>
<p>Easy configuration: Ruff allows you to configure your analysis in the pyproject.toml file. This makes it easier to set up and customize your analysis as all the configuration is stored in a single file.</p>
<p>Auto-fix feature: Ruff also has a unique auto-fix feature that allows you to automatically fix common problems with a single command. The --fix option can be used to automatically fix problems that Ruff has identified in your code. This saves you time and effort, allowing you to focus on more important tasks.</p>
<h2 id="heading-getting-started-with-ruff">Getting started with Ruff</h2>
<p>Getting started with Ruff is simple. You can install Ruff using pip, the Python package manager. Just run the following command in your terminal</p>
<pre><code class="lang-bash">pip install ruff
</code></pre>
<p>Once Ruff is installed, you can configure your analysis in the <code>pyproject.toml</code> file. Here is an example configuration:</p>
<pre><code class="lang-ini"><span class="hljs-section">[tool.ruff]</span>
<span class="hljs-comment"># Docs: https://beta.ruff.rs/docs/</span>
<span class="hljs-comment"># Rules: https://beta.ruff.rs/docs/rules/</span>
<span class="hljs-attr">select</span> = [<span class="hljs-string">"B"</span>, <span class="hljs-string">"C4"</span>, <span class="hljs-string">"EXE"</span>, <span class="hljs-string">"F"</span>, <span class="hljs-string">"E"</span>, <span class="hljs-string">"ISC"</span>, <span class="hljs-string">"ICN"</span>, <span class="hljs-string">"INP"</span>, <span class="hljs-string">"PIE"</span>, <span class="hljs-string">"SIM"</span>, <span class="hljs-string">"W"</span>, <span class="hljs-string">"T20"</span>, <span class="hljs-string">"UP"</span>, <span class="hljs-string">"T10"</span>, <span class="hljs-string">"G"</span>, <span class="hljs-string">"C90"</span>, <span class="hljs-string">"ERA"</span>]
<span class="hljs-attr">ignore</span> = [<span class="hljs-string">"B008"</span>, <span class="hljs-string">"SIM102"</span>]
<span class="hljs-attr">fixable</span> = [<span class="hljs-string">"F"</span>, <span class="hljs-string">"E"</span>, <span class="hljs-string">"B"</span>, <span class="hljs-string">"C4"</span>, <span class="hljs-string">"EXE"</span>, <span class="hljs-string">"ISC"</span>, <span class="hljs-string">"ICN"</span>, <span class="hljs-string">"INP"</span>, <span class="hljs-string">"PIE"</span>, <span class="hljs-string">"SIM"</span>, <span class="hljs-string">"W"</span>, <span class="hljs-string">"T20"</span>, <span class="hljs-string">"UP"</span>]
<span class="hljs-attr">unfixable</span> = []

<span class="hljs-comment"># Exclude a variety of commonly ignored directories.</span>
<span class="hljs-attr">exclude</span> = [
    <span class="hljs-string">".git"</span>,
    <span class="hljs-string">".mypy_cache"</span>,
    <span class="hljs-string">".pre-commit-cache"</span>,
    <span class="hljs-string">".ruff_cache"</span>,
    <span class="hljs-string">".tox"</span>,
    <span class="hljs-string">".venv"</span>,
    <span class="hljs-string">"venv"</span>,
    <span class="hljs-string">"docs"</span>,
    <span class="hljs-string">"__pycache"</span>,
    <span class="hljs-string">"**/migrations/*"</span>,
]

<span class="hljs-comment"># Same as Black.</span>
<span class="hljs-attr">line-length</span> = <span class="hljs-number">100</span>

<span class="hljs-comment"># Allow unused variables when underscore-prefixed.</span>
<span class="hljs-attr">dummy-variable-rgx</span> = <span class="hljs-string">"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"</span>
<span class="hljs-comment"># Assume Python 3.11.</span>
<span class="hljs-attr">target-version</span> = <span class="hljs-string">"py311"</span>

<span class="hljs-section">[tool.ruff.mccabe]</span>
<span class="hljs-comment"># Unlike Flake8, default to a complexity level of 10.</span>
<span class="hljs-attr">max-complexity</span> = <span class="hljs-number">10</span>
</code></pre>
<p>For a full list of Ruff rules check it out <a target="_blank" href="https://beta.ruff.rs/docs/rules/">here</a>.</p>
<p>To run Ruff, simply navigate to your project directory in the terminal and run the following command:</p>
<pre><code class="lang-bash">ruff check .
</code></pre>
<p>Ruff will analyze your code and display any errors or warnings it has found. If you want to automatically fix common issues, you can use the <code>--fix</code> option:</p>
<pre><code class="lang-bash">ruff check . --fix
</code></pre>
<p>Ruff will automatically fix any problems it finds, saving you time and effort.</p>
<h2 id="heading-vscode-extension">VSCode extension</h2>
<p>Ruff also has a Visual Studio Code (VSCode) <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff">extension</a> that makes it even easier to use. The VSCode extension provides real-time analysis of your code, allowing you to quickly identify and fix problems.</p>
<h2 id="heading-pre-commit-integration">Pre-commit integration</h2>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/charliermarsh/ruff-pre-commit</span>
  <span class="hljs-comment"># Ruff version.</span>
  <span class="hljs-attr">rev:</span> <span class="hljs-string">'v0.1.13'</span>
  <span class="hljs-attr">hooks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">ruff</span>
</code></pre>
<h2 id="heading-the-bottom-line">The bottom line</h2>
<p>In conclusion, Ruff is a fast and powerful static code analysis tool for Python. Its fast performance, comprehensive analysis and easy configuration make it an excellent choice for any Python project looking to improve code quality and avoid common pitfalls.</p>
]]></content:encoded></item><item><title><![CDATA[Building a Basic Kafka Application with Python]]></title><description><![CDATA[Kafka is a distributed streaming platform that is used for building real-time data pipelines and streaming apps. It is a powerful tool that can handle high volumes of data and allows for the storage and processing of streams in a fault-tolerant and s...]]></description><link>https://blog.mounirmesselmeni.de/building-a-basic-kafka-application-with-python</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/building-a-basic-kafka-application-with-python</guid><category><![CDATA[kafka]]></category><category><![CDATA[Python]]></category><category><![CDATA[Confluent Kafka]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sat, 14 Jan 2023 15:35:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1678143630855/faf533f9-48cd-4ebc-a577-7c53473b2896.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kafka is a distributed streaming platform that is used for building real-time data pipelines and streaming apps. It is a powerful tool that can handle high volumes of data and allows for the storage and processing of streams in a fault-tolerant and scalable manner. In this article, we will go through the steps of setting up Kafka via Docker and Docker Compose, and then use the python Confluent Kafka library to build an example producer and consumer.</p>
<h3 id="heading-install-docker-and-docker-compose">Install Docker and Docker Compose</h3>
<p>To get started, you will need to have Docker and Docker Compose installed on your machine. If you don't have them installed, you can download them from the Docker website. Once you have them installed, you can proceed to the next step.</p>
<h3 id="heading-create-a-docker-compose-file">Create a Docker Compose file</h3>
<p>Create a new directory on your machine, and within that directory, create a new file called "docker-compose.yml". This file will be used to define the services that make up our Kafka cluster.</p>
<h3 id="heading-define-the-kafka-service">Define the Kafka Service</h3>
<p>In the "docker-compose.yml" file, add the following code to define the Kafka service:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">kafka:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">confluentinc/cp-kafka:7.3.0</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_LISTENERS:</span> <span class="hljs-string">PLAINTEXT://kafka:29092,CONNECTIONS_FROM_HOST://localhost:9092</span>
      <span class="hljs-attr">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:</span> <span class="hljs-string">PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT</span>
      <span class="hljs-attr">KAFKA_ZOOKEEPER_CONNECT:</span> <span class="hljs-string">zookeeper:2181</span>
      <span class="hljs-attr">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS:</span> <span class="hljs-number">0</span>
      <span class="hljs-attr">KAFKA_TRANSACTION_STATE_LOG_MIN_ISR:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">KAFKA_LOG_FLUSH_INTERVAL_MESSAGES:</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">9092</span><span class="hljs-string">:9092</span>
</code></pre>
<p>This code defines a single service called "kafka" that uses the Confluent Kafka image version 5.5.1. It also sets the environment variables that are used to configure the Kafka service.</p>
<h3 id="heading-define-the-zookeeper-service">Define the Zookeeper Service</h3>
<p>Add the following code to the "docker-compose.yml" file to define the Zookeeper service:</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">zookeeper:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">confluentinc/cp-zookeeper:7.3.0</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">ZOOKEEPER_CLIENT_PORT:</span> <span class="hljs-number">2181</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">2181</span><span class="hljs-string">:2181</span>
</code></pre>
<p>This code defines a single service called "zookeeper" that uses the Confluent Zookeeper image version 5.5.1. It also sets the environment variable "ZOOKEEPER_CLIENT_PORT" which is used to configure the Zookeeper service.</p>
<h3 id="heading-start-the-services">Start the Services</h3>
<p>Now that we have defined our services, we can start them by running the following command in the same directory as the "docker-compose.yml" file:</p>
<pre><code class="lang-bash">docker-compose up
</code></pre>
<p>This command will start the Kafka and zookeeper services defined in the "docker-compose.yml" file.</p>
<h3 id="heading-install-the-confluent-kafka-library">Install the Confluent Kafka library</h3>
<p>Make sure to <a target="_blank" href="https://github.com/confluentinc/librdkafka#installation">install librdkafka</a> which is used by the confluent kafka python package.</p>
<p>To do so in macOS: <code>brew install librdkafka</code></p>
<p>To interact with the Kafka cluster, we will use the python Confluent Kafka library. To install it, run the following poetry command (you can use pip too):</p>
<pre><code class="lang-bash">poetry add confluent-kafka
</code></pre>
<h3 id="heading-creating-topics-with-the-python-client">Creating topics with the python client</h3>
<p><code>create_topic.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">from</span> confluent_kafka.admin <span class="hljs-keyword">import</span> AdminClient, NewTopic

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_topic</span>(<span class="hljs-params">topic_name</span>):</span>
    conf = {<span class="hljs-string">'bootstrap.servers'</span>: <span class="hljs-string">'localhost:9092'</span>}
    admin_client = AdminClient(conf)
    new_topic = admin_client.create_topics([NewTopic(topic_name, num_partitions=<span class="hljs-number">1</span>, replication_factor=<span class="hljs-number">1</span>)])
    <span class="hljs-keyword">for</span> topic, future <span class="hljs-keyword">in</span> new_topic.items():
        <span class="hljs-keyword">try</span>:
            future.result()
            print(<span class="hljs-string">"Topic {} created successfully"</span>.format(topic))
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            print(<span class="hljs-string">"Failed to create topic {}: {}"</span>.format(topic, e))

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    <span class="hljs-keyword">if</span> len(sys.argv) != <span class="hljs-number">2</span>:
        print(<span class="hljs-string">"Usage: python {} &lt;topic_name&gt;"</span>.format(sys.argv[<span class="hljs-number">0</span>]))
        sys.exit(<span class="hljs-number">1</span>)
    topic_name = sys.argv[<span class="hljs-number">1</span>]
    create_topic(topic_name)
</code></pre>
<p>This script uses the <code>AdminClient</code> class from the Confluent Kafka package to create a new topic. It takes a topic name as input, creates a new topic with that name, 1 partition and replication factor 1. If the topic is created successfully, it will print "Topic topic_name"</p>
<h3 id="heading-create-a-producer">Create a producer</h3>
<p>Create a new python file and import the confluent_kafka library, then create a function that will be responsible for producing messages to the Kafka topic.</p>
<p>`produce_messages.py`</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">from</span> confluent_kafka <span class="hljs-keyword">import</span> Producer

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">produce_message</span>(<span class="hljs-params">topic_name</span>):</span>
    conf = {<span class="hljs-string">'bootstrap.servers'</span>: <span class="hljs-string">'localhost:9092'</span>}
    producer = Producer(conf)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delivery_callback</span>(<span class="hljs-params">err, msg</span>):</span>
        <span class="hljs-keyword">if</span> err:
            print(<span class="hljs-string">'Message delivery failed: {}'</span>.format(err))
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">'Message delivered to {} [{}]'</span>.format(msg.topic(), msg.partition()))

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">10</span>):
        message = <span class="hljs-string">'This is message number {}'</span>.format(i)
        producer.produce(topic_name, value=message.encode(<span class="hljs-string">'utf-8'</span>), callback=delivery_callback)
        producer.poll(<span class="hljs-number">0</span>)
        <span class="hljs-comment"># flush is only needed if you want to synchronously ensure the message was received, but the producer is async</span>
        <span class="hljs-comment">#producer.flush()</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    <span class="hljs-keyword">if</span> len(sys.argv) != <span class="hljs-number">2</span>:
        print(<span class="hljs-string">"Usage: python {} &lt;topic_name&gt;"</span>.format(sys.argv[<span class="hljs-number">0</span>]))
        sys.exit(<span class="hljs-number">1</span>)
    topic_name = sys.argv[<span class="hljs-number">1</span>]
    produce_message(topic_name)
</code></pre>
<p>This function takes a topic name as an input and creates a producer object with the bootstrap servers configuration set to "<a target="_blank" href="http://localhost:29092">localhost:29092</a>" (which is the default port for Kafka on a single node setup). It then creates a for loop to produce 10 messages to the topic with a simple string value.</p>
<h3 id="heading-create-a-consumer">Create a consumer</h3>
<p><code>consume_messages.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">from</span> confluent_kafka <span class="hljs-keyword">import</span> Consumer, KafkaError

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">consume_message</span>(<span class="hljs-params">topic_name</span>):</span>
    conf = {<span class="hljs-string">'bootstrap.servers'</span>: <span class="hljs-string">'localhost:9092'</span>,
            <span class="hljs-string">'group.id'</span>: <span class="hljs-string">'mygroup'</span>,
            <span class="hljs-string">'auto.offset.reset'</span>: <span class="hljs-string">'earliest'</span>,
            }
    consumer = Consumer(conf)
    consumer.subscribe([topic_name])
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            msg = consumer.poll(<span class="hljs-number">1.0</span>)
            <span class="hljs-keyword">if</span> msg <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
                <span class="hljs-keyword">continue</span>
            <span class="hljs-keyword">if</span> msg.error():
                <span class="hljs-keyword">if</span> msg.error().code() == KafkaError._PARTITION_EOF:
                    print(<span class="hljs-string">'Reached end of topic {} [{}] at offset {}'</span>.format(msg.topic(), msg.partition(), msg.offset()))
                <span class="hljs-keyword">else</span>:
                    print(<span class="hljs-string">'Error occured: {}'</span>.format(msg.error()))
            <span class="hljs-keyword">else</span>:
                print(<span class="hljs-string">'Received message: {}'</span>.format(msg.value()))
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Exiting..."</span>)
    <span class="hljs-keyword">finally</span>:
        consumer.close()

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    <span class="hljs-keyword">if</span> len(sys.argv) != <span class="hljs-number">2</span>:
        print(<span class="hljs-string">"Usage: python {} &lt;topic_name&gt;"</span>.format(sys.argv[<span class="hljs-number">0</span>]))
        sys.exit(<span class="hljs-number">1</span>)
    topic_name = sys.argv[<span class="hljs-number">1</span>]
    consume_message(topic_name)
</code></pre>
<p>This script uses the <code>Consumer</code> class from the Confluent Kafka package to consume messages from a Kafka topic. It takes a topic name as input, creates a consumer for that topic, and enters a while loop to poll for new messages. If a message is received, it is printed to the console.</p>
<p>This script will only display the messages, you can add some logic to handle them like storing them in a database for example.</p>
<p>Also, you can run this script after running the previous script of producing messages to see the messages being consumed.</p>
<h3 id="heading-run-the-examples">Run the examples</h3>
<p>Now that we have created our producer and consumer functions, you can run them and see the messages being produced and consumed.</p>
<p>Create the test topic:</p>
<pre><code class="lang-bash">poetry run python create_topic.py testtopic
</code></pre>
<p>Run the consume message script</p>
<pre><code class="lang-bash">poetry run python consume_messages.py testtopic
</code></pre>
<p>Keep it running and produce some message using:</p>
<pre><code class="lang-bash">poetry run python produce_messages.py testtopic
</code></pre>
<p>You should see the message output in the consumer command:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673699200295/978ea565-c590-4ccc-a3e9-696c9f523ea1.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In conclusion, setting up Kafka via Docker and Docker Compose is a straightforward process. By using the Confluent Kafka library, it is also easy to interact with the Kafka cluster and build useful producers and consumers. With this setup, you can quickly spin up a Kafka cluster for testing and development purposes and easily add more nodes to the cluster when needed.</p>
<p>Full source code on GitHub.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/mounirmesselmeni/python-kafka-example">https://github.com/mounirmesselmeni/python-kafka-example</a></div>
]]></content:encoded></item><item><title><![CDATA[Unified Django development toolkit for your team using VSCode - Part 1]]></title><description><![CDATA[docker compose up -d is amazing right?
You don't know what is does? In simple words it will create required services to make a specific project up and running.
I will not cover basic knowledge about docker and docker compose, there are plenty of tuto...]]></description><link>https://blog.mounirmesselmeni.de/unified-django-development-toolkit-for-your-team-using-vscode-part-1</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/unified-django-development-toolkit-for-your-team-using-vscode-part-1</guid><category><![CDATA[vscode extensions]]></category><category><![CDATA[vscode]]></category><category><![CDATA[Django]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Docker compose]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sat, 22 Oct 2022 11:27:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/tjX_sniNzgQ/upload/v1666380964714/32bHh8T4q.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><code>docker compose up -d</code> is amazing right?</p>
<p>You don't know what is does? In simple words it will create required services to make a specific project up and running.</p>
<p>I will not cover basic knowledge about docker and docker compose, there are plenty of tutorials/videos doing deep introduction for this.</p>
<h2 id="heading-objectif">Objectif</h2>
<p>We will have a VSCode + Docker compose setup which will run the project in VSCode dev containers, this will make sure that all services are up and running (DB, Django's runserver, Cache ...)
We will also setup useful VSCode extensions which will boost productivity and will be available for anyone using the project with VSCode.</p>
<h2 id="heading-requirements">Requirements</h2>
<ul>
<li>Linux (docker + VSCode + VSCode Dev Containers extension)</li>
<li>Mac OS (Docker Desktop + VSCode Dev Containers extension)</li>
<li><p>Windows (Docker Desktop + VSCode Dev Containers extension)</p>
</li>
<li><p><a target="_blank" href="https://docs.docker.com/engine/install/ubuntu/">Docker</a></p>
</li>
<li><a target="_blank" href="https://docs.docker.com/get-docker/">Docker Desktop</a></li>
<li><a target="_blank" href="https://code.visualstudio.com/download">VSCode</a></li>
<li><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">Dev Containers extension</a></li>
</ul>
<p>// Mac M1/M2 users please use Apple Silicon version for Docker Desktop and VSCode for a better performance</p>
<h2 id="heading-vscode-dev-container-developing-inside-a-container">VSCode dev container (Developing inside a Container)</h2>
<h3 id="heading-why">Why?</h3>
<ul>
<li>How many time did you have to install virtualenv in your machine to enable full features of the Django project you're working on? </li>
<li>Wasn't you annoyed of how you will keep them up to date if your team use Docker or/and Docker compose and sync them.</li>
<li>What about installing system dependencies needed for compilation for some scenarios (python dev, imaging libraries, librdkafka, libmemcached-dev, postgresql headers if not using psycopg2-binary etc)? </li>
<li>And how much time did you already spend on this and you will spend on each new install of your OS or some new upgrades?</li>
</ul>
<p>What about the time spent by your team or team members on that and specially when using different OS (Mac, Linux, Windows)?</p>
<p>What about opening VSCode (No terminal is needed) and then everything is setup for you out of the box, you will only need git, docker/docker desktop and that's all !</p>
<p>This will also keep your machine clean from different dependencies, you don't have to switch between dependencies version or system packages version, Dockerize everything!</p>
<p>For a full documentation on setting up dev containers please check this <a target="_blank" href="https://code.visualstudio.com/docs/remote/containers">official link</a>. I will later on refer to some section to it.</p>
<h2 id="heading-dummy-django-project">Dummy Django project</h2>
<p>We will start by cloning a dummy Django project which don't do much and try to add <code>Dockerfile</code>, <code>docker-compose.yml</code> and dev container setup to it.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> --branch initial_project_setup https://github.com/MounirMesselmeni/django-vscode-dev-containers.git
</code></pre>
<p>This a simple Django project containing the required setup with <code>Dockerfile</code> and <code>docker-compose.yml</code>, we use postgres image for database</p>
<h3 id="heading-project-structure">Project structure</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435087041/2bLhktO6A.png" alt="image.png" /></p>
<p>Dockerfile</p>
<pre><code class="lang-dockerfile">FROM python:3.10

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV LC_ALL=C.UTF-8

WORKDIR /app

COPY requirements.txt /app/

RUN pip install -r requirements.txt
</code></pre>
<p><code>docker-compose.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.9"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">django:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">./Dockerfile</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">python</span> <span class="hljs-string">manage.py</span> <span class="hljs-string">runserver</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:8000</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8000</span><span class="hljs-string">:8000</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app:cached</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">postgres:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:15.0-alpine</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=app</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=app</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=app</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-comment"># with named volume we will persist database state E.g after restarting docker/machine</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres-data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">postgres-data:</span>
</code></pre>
<p>Add a <code>.env</code> file under <code>django_vscode_dev_containers</code> folder</p>
<pre><code>DEBUG=yes
# SECURITY WARNING: keep the secret key used <span class="hljs-keyword">in</span> production secret!
SECRET_KEY=django-insecure-qk0&amp;tbbj(&amp;c9x^t6wu6wkwwg@xox^t0*@(dbo3et03x*+<span class="hljs-number">1</span>-avy
ALLOWED_HOSTS=localhost,<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>,::<span class="hljs-number">1</span>,<span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>
DATABASE_URL=psql:<span class="hljs-comment">//app:app@postgres:5432/app</span>
</code></pre><h2 id="heading-setting-up-dev-containers">Setting up dev containers</h2>
<p>We can do that using VSCode command palette and dev containers extension helper but for the sake of simplicity we will do this by hand.</p>
<p>Create a .devcontainer/devcontainer.json file in the main directory</p>
<pre><code class="lang-json"><span class="hljs-comment">// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:</span>
<span class="hljs-comment">// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/docker-existing-docker-compose</span>
<span class="hljs-comment">// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.</span>
{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Django VSCode Dev Containers"</span>,

    <span class="hljs-comment">// Update the 'dockerComposeFile' list if you have more compose files or use different names.</span>
    <span class="hljs-comment">// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.</span>
    <span class="hljs-attr">"dockerComposeFile"</span>: [
        <span class="hljs-string">"../docker-compose.yml"</span>,
        <span class="hljs-string">"docker-compose.yml"</span>
    ],

    <span class="hljs-comment">// The 'service' property is the name of the service for the container that VS Code should</span>
    <span class="hljs-comment">// use. Update this value and .devcontainer/docker-compose.yml to the real service name.</span>
    <span class="hljs-attr">"service"</span>: <span class="hljs-string">"django"</span>,

    <span class="hljs-comment">// The optional 'workspaceFolder' property is the path VS Code should open by default when</span>
    <span class="hljs-comment">// connected. This is typically a file mount in .devcontainer/docker-compose.yml</span>
    <span class="hljs-attr">"workspaceFolder"</span>: <span class="hljs-string">"/app"</span>,

    <span class="hljs-attr">"runServices"</span>: [
        <span class="hljs-string">"django"</span>,
        <span class="hljs-string">"postgres"</span>
    ]

    <span class="hljs-comment">// Uncomment the next line if you want to keep your containers running after VS Code shuts down.</span>
    <span class="hljs-comment">// "shutdownAction": "none",</span>

    <span class="hljs-comment">// Uncomment the next line to run commands after the container is created - for example installing curl.</span>
    <span class="hljs-comment">// "postCreateCommand": "apt-get update &amp;&amp; apt-get install -y curl",</span>

    <span class="hljs-comment">// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.</span>
    <span class="hljs-comment">// "remoteUser": "vscode"</span>
}
</code></pre>
<p>Note on runServices, we define here which services we want VSCode to make sure they are up and running, we could have a redis service which we don't need, or it's essential to the proper functioning of the application so we would like VSCode to take care of running it with docker/docker-compose.</p>
<p>And <code>devcontainers/docker-compose.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.9'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-comment"># Update this to the name of the service you want to work with in your docker-compose.yml file</span>
  <span class="hljs-attr">django:</span>
    <span class="hljs-comment"># If you want add a non-root user to your Dockerfile, you can use the "remoteUser"</span>
    <span class="hljs-comment"># property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, </span>
    <span class="hljs-comment"># debugging) to execute as the user. Uncomment the next line if you want the entire </span>
    <span class="hljs-comment"># container to run as this user instead. Note that, on Linux, you may need to </span>
    <span class="hljs-comment"># ensure the UID and GID of the container user you create matches your local user. </span>
    <span class="hljs-comment"># See https://aka.ms/vscode-remote/containers/non-root for details.</span>
    <span class="hljs-comment">#</span>
    <span class="hljs-comment"># user: vscode</span>

    <span class="hljs-comment"># Overrides default command so things don't shut down after the process ends.</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">/bin/sh</span> <span class="hljs-string">-c</span> <span class="hljs-string">"while sleep 1000; do :; done"</span>
</code></pre>
<p>Open the Command Palette (Ctrl/Cmd+shift+p) and search for Rebuild and reopen in Container</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435032187/6w7aHv0gT.png" alt="image.png" /></p>
<p>Now VSCode will open the project inside the container, we can see the docker building command and output by clicking on <code>Starting Dev container show logs</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435253442/AVUIzoxtx.png" alt="image.png" /></p>
<p>The logs would look like this
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435362907/0RuYjoVQ9.png" alt="image.png" /></p>
<p>We know that we are developing inside a container in the bottom left corner we can see the name we provided in <code>devcontainer.json</code> file
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435439244/FTGz1ezHE.png" alt="image.png" /></p>
<h2 id="heading-setting-up-python-extensions">Setting up python extensions</h2>
<p>Now we need more productivity by setting up proper python extensions to use code completion etc with Pylance.</p>
<p>Let's add required extensions list into the <code>.devcontainers/devcontainer.json</code> and some custom vscode setting (Yes you can define VSCode settings in dev container and share them with others)</p>
<pre><code class="lang-json">  <span class="hljs-string">"settings"</span>: {
        <span class="hljs-attr">"python.terminal.activateEnvironment"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">"python.terminal.activateEnvInCurrentTerminal"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"python.pythonPath"</span>: <span class="hljs-string">"/usr/local/bin/python"</span>,
        <span class="hljs-attr">"python.defaultInterpreterPath"</span>: <span class="hljs-string">"/usr/local/bin/python"</span>,
        <span class="hljs-attr">"python.languageServer"</span>: <span class="hljs-string">"Pylance"</span>,
        <span class="hljs-comment">// file formatting options</span>
        <span class="hljs-attr">"files.trimTrailingWhitespace"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"files.insertFinalNewline"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-comment">// files to exclude from all checks</span>
        <span class="hljs-attr">"files.exclude"</span>: {
            <span class="hljs-attr">"**/*.pyc"</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">"**/.git"</span>: <span class="hljs-literal">false</span>,
            <span class="hljs-attr">"**/migrations/*"</span>: <span class="hljs-literal">false</span>
        },
        <span class="hljs-attr">"python.analysis.extraPaths"</span>: [
            <span class="hljs-string">"/usr/local/lib/python3.10/site-packages/"</span>
        ],
        <span class="hljs-attr">"python.analysis.useImportHeuristic"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"python.analysis.autoSearchPaths"</span>: <span class="hljs-literal">true</span>
    },
   <span class="hljs-string">"extensions"</span>: [
        <span class="hljs-string">"ms-python.python"</span>,
        <span class="hljs-string">"ms-python.vscode-pylance"</span>,
        <span class="hljs-string">"aaron-bond.better-comments"</span>,
        <span class="hljs-string">"visualstudioexptteam.vscodeintellicode"</span>,
        <span class="hljs-string">"mikestead.dotenv"</span>
    ]
</code></pre>
<p>Command Palette and search for Rebuild to rebuilt the container and restart:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435659207/fUJ4Qm7c6.png" alt="image.png" /></p>
<p>Now we can see VSCode is installing the extensions inside the dev container</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666435773793/7FZS4e5DR.png" alt="image.png" /></p>
<p>A common problem to this, that sometime Pylance does not work properly from the first time and you need to restart or rebuild, and everytime you add a requirement or change dockerfile VSCode will re-install all the extensions.
To fix this we can avoid <a target="_blank" href="https://code.visualstudio.com/remote/advancedcontainers/avoid-extension-reinstalls">VSCode extensions reinstalls</a> by adding a persisted volume for those extensions.</p>
<p>Let's modify our Dockerfile to create required extensions folder</p>
<pre><code class="lang-dockerfile">FROM python:3.10

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV LC_ALL=C.UTF-8

# Yes we are using the root user now, it's not encouraged but this is only for demo purposes
RUN mkdir -p /root/.vscode-server/extensions /root/.vscode-server-insiders/extensions

WORKDIR /app

COPY requirements.txt /app/

RUN pip install -r requirements.txt
</code></pre>
<p>And add the new docker volume in the <code>.devcontainer/docker-compose.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.9'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-comment"># Update this to the name of the service you want to work with in your docker-compose.yml file</span>
  <span class="hljs-attr">django:</span>
    <span class="hljs-comment"># If you want add a non-root user to your Dockerfile, you can use the "remoteUser"</span>
    <span class="hljs-comment"># property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, </span>
    <span class="hljs-comment"># debugging) to execute as the user. Uncomment the next line if you want the entire </span>
    <span class="hljs-comment"># container to run as this user instead. Note that, on Linux, you may need to </span>
    <span class="hljs-comment"># ensure the UID and GID of the container user you create matches your local user. </span>
    <span class="hljs-comment"># See https://aka.ms/vscode-remote/containers/non-root for details.</span>
    <span class="hljs-comment">#</span>
    <span class="hljs-comment"># user: vscode</span>

    <span class="hljs-comment"># Overrides default command so things don't shut down after the process ends.</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">/bin/sh</span> <span class="hljs-string">-c</span> <span class="hljs-string">"while sleep 1000; do :; done"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">vscode_extensions:/root/.vscode-server/extensions</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">vscode_extensions:</span>
</code></pre>
<p>Now rebuild again using commands palette</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666436177542/9GiAYvFAC.png" alt="image.png" /></p>
<p>We can re-run Rebuild without Cache just to test out if the extensions changes are working properly
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666436316384/MOZzc4eNT.png" alt="image.png" /></p>
<p>Now we don't re-install extensions as they are persisted in the docker volume</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666436444586/8x23aUAnG.png" alt="image.png" /></p>
<p>We should be able now to have autocompletion working and navigating/going to third party packages like <code>environ</code> in settings.py with Cmd/Ctrl click</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666436737315/mih-cSfaf.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666436772350/0t8Cq3xOS.png" alt="image.png" /></p>
<p>To go inside the container (Our case the django/python one) you click in the + button as shown bellow</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666437097626/OV5dzDydx.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666437111984/6BFCwFknt.png" alt="image.png" /></p>
<p>We can run our runserver from there</p>
<pre><code class="lang-bash">./manage.py runserver 0:8000
</code></pre>
<p>And access <a target="_blank" href="http://localhost:8000">localhost:8000</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666437182209/uTys2OVpY.png" alt="image.png" /></p>
<h2 id="heading-end">End</h2>
<p>Nice what do you think?</p>
<p>This might looks like a lot of work, but you will set it up once and after it's mostly copy paste and you have your environment easy to work with your colleagues/teammates and simplify the process.</p>
<p>Final result could be find in the github repo:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/MounirMesselmeni/django-vscode-dev-containers">https://github.com/MounirMesselmeni/django-vscode-dev-containers</a></div>
<h2 id="heading-part-2-next">Part 2 (Next)</h2>
<ul>
<li>I will add a part 2 soon in which we will cover how to use git from inside the container, this is handy if you want to have some <a target="_blank" href="https://blog.mounirmesselmeni.de/pre-commit-your-django-projects">pre-commit scripts</a> and you have no interest to install that on the host machine or if you like using the git integration inside VSCode.</li>
<li>Setup automatic formatting using black - On save</li>
<li>Setup automatic imports sorting using isort - On save</li>
<li>Pylint/Flake8 previews inside VSCode</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[pre-commit your Django projects]]></title><description><![CDATA[Updated 08/02/2024: Updated example dependencies, use Ruff instead of flake8 and pylint
Pre-commit is a python based tool to enable easy integration of git hooks, and it's supported by plenty of tools like ruff, black, ...
While coding a Django proje...]]></description><link>https://blog.mounirmesselmeni.de/pre-commit-your-django-projects</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/pre-commit-your-django-projects</guid><category><![CDATA[Django]]></category><category><![CDATA[Git]]></category><category><![CDATA[Code Quality]]></category><category><![CDATA[Python]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[ruff]]></category><category><![CDATA[pre-commit]]></category><category><![CDATA[Linting]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sat, 15 Oct 2022 19:52:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/FHnnjk1Yj7Y/upload/v1665862780502/s3zjGdHZc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Updated 08/02/2024: Updated example dependencies, use Ruff instead of flake8 and pylint</p>
<p><a target="_blank" href="https://pre-commit.com/">Pre-commit</a> is a python based tool to enable easy integration of git hooks, and it's supported by plenty of tools like ruff, black, ...</p>
<p>While coding a Django project in a team, you would like to have unified rules and styles to avoid unnecessary changes on each Pull Request another team member will review. After setting up for the first time, this is for me a must for every project to ensure consistency and code quality.</p>
<p>After installing pre-commit, you will need to create a <code>.pre-commit-config.yaml</code> file and run <code>pre-commit install</code> inside the project to activate the git hooks.</p>
<h2 id="heading-default-pre-commit-hooks">Default pre-commit hooks</h2>
<pre><code class="lang-yaml"><span class="hljs-attr">repos:</span>
<span class="hljs-bullet">-</span>   <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/pre-commit/pre-commit-hooks</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">v4.5.0</span>
    <span class="hljs-attr">hooks:</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">check-yaml</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">end-of-file-fixer</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">trailing-whitespace</span>
<span class="hljs-bullet">-</span>   <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/psf/black</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">24.1</span><span class="hljs-number">.1</span>
    <span class="hljs-attr">hooks:</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">black</span>
</code></pre>
<p>Check <a target="_blank" href="https://pre-commit.com/hooks.html">this link</a> for a full list of builtins hooks from pre-commit.</p>
<h2 id="heading-isort">isort</h2>
<p>The initial example contains configuration of python <a target="_blank" href="https://black.readthedocs.io/">Black</a> formatting, on top of it I strongly recommend using consistent sorted import via <a target="_blank" href="https://github.com/pycqa/isort">isort</a></p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/pycqa/isort</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">5.13</span><span class="hljs-number">.2</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">isort</span>
</code></pre>
<p>If you use isort with black you will need to use black profile as follow in <code>pyproject.toml</code> in your root project directory.</p>
<pre><code class="lang-toml"><span class="hljs-section">[tool.black]</span>
<span class="hljs-attr">line-length</span> = <span class="hljs-number">88</span>
<span class="hljs-attr">include</span> = <span class="hljs-string">'\.pyi?$'</span>

<span class="hljs-section">[tool.isort]</span>
<span class="hljs-attr">profile</span> = <span class="hljs-string">"black"</span>
<span class="hljs-attr">py_version</span>=<span class="hljs-number">311</span>
<span class="hljs-attr">multi_line_output</span> = <span class="hljs-number">3</span>
<span class="hljs-attr">line_length</span> = <span class="hljs-number">88</span>
<span class="hljs-attr">default_section</span> = <span class="hljs-string">"THIRDPARTY"</span>
<span class="hljs-attr">skip</span> = [<span class="hljs-string">"migrations"</span>]
</code></pre>
<h2 id="heading-pyupgrade">Pyupgrade</h2>
<p>Another amazing tool you might need to use is <a target="_blank" href="https://github.com/asottile/pyupgrade">pyupgrade</a>, this one will ensure you will use new python feature like new style classes, removing unicode literals, use the python3 super() calls etc.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/asottile/pyupgrade</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">v3.15.0</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">pyupgrade</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">"--py3-plus"</span>, <span class="hljs-string">"--py311-plus"</span>]
</code></pre>
<p>If you use python 3.10 you can change <code>--py39-plus</code> with <code>--py310plus</code></p>
<h2 id="heading-djangoupgrade">Djangoupgrade</h2>
<p>Next similar tools is <a target="_blank" href="https://github.com/adamchainz/django-upgrade">djangoupgrade</a>, it will fix deprecated features of Django and make it easy to upgrade your Django projects, unfortunately is does only check against python files, for Django templates it's not doing anything, but I still find it useful.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/adamchainz/django-upgrade</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">"1.15.0"</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">django-upgrade</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">--target-version</span>, <span class="hljs-string">"5.0"</span>]
</code></pre>
<p>Note the target-version, this depend on the current supported Django version in the project.</p>
<h2 id="heading-bandit">Bandit</h2>
<p>Another great tool to use is <a target="_blank" href="https://github.com/pycqa/bandit">Bandit</a>, Bandit is designed to find common security issues in Python code. To do this Bandit processes each file, builds an AST from it, and runs appropriate plugins against the AST nodes. Once Bandit has finished scanning all the files it generates a report.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/pycqa/bandit</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">1.7</span><span class="hljs-number">.7</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">bandit</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">"-iii"</span>, <span class="hljs-string">"-ll"</span>]
</code></pre>
<h2 id="heading-ruff">Ruff</h2>
<p><a target="_blank" href="https://docs.astral.sh/ruff/">Ruff</a> is a powerful Python tool that consolidates various code quality checkers like pycodestyle, pyflakes, and mccabe, along with third-party plugins. It ensures the style and quality of Python code through a unified interface.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/charliermarsh/ruff-pre-commit</span>
    <span class="hljs-comment"># Ruff version.</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">"v0.2.1"</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">ruff</span>
</code></pre>
<p>Now we will need to setup Ruff config in <code>pyproject.toml</code></p>
<pre><code class="lang-ini"><span class="hljs-section">[tool.ruff]</span>
<span class="hljs-attr">line-length</span> = <span class="hljs-number">88</span>
<span class="hljs-attr">target-version</span> = <span class="hljs-string">"py311"</span>
<span class="hljs-attr">exclude</span> = [
    <span class="hljs-string">".git"</span>,
    <span class="hljs-string">".mypy_cache"</span>,
    <span class="hljs-string">".pre-commit-cache"</span>,
    <span class="hljs-string">".ruff_cache"</span>,
    <span class="hljs-string">".tox"</span>,
    <span class="hljs-string">".venv"</span>,
    <span class="hljs-string">"venv"</span>,
    <span class="hljs-string">"docs"</span>,
    <span class="hljs-string">"__pycache"</span>,
    <span class="hljs-string">"**/migrations/*"</span>,
]
<span class="hljs-attr">dummy-variable-rgx</span> = <span class="hljs-string">"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"</span>

<span class="hljs-section">[tool.ruff.lint.mccabe]</span>
<span class="hljs-attr">max-complexity</span> = <span class="hljs-number">10</span>
</code></pre>
<p>To run ruff manually, once installed (e.g <code>pip install ruff</code>)</p>
<pre><code class="lang-bash">ruff check .
</code></pre>
<h1 id="heading-final-result">Final result</h1>
<p>The final <code>.pre-commit-config.yaml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">repos:</span>
<span class="hljs-bullet">-</span>   <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/pre-commit/pre-commit-hooks</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">v4.5.0</span>
    <span class="hljs-attr">hooks:</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">check-yaml</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">end-of-file-fixer</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">trailing-whitespace</span>
<span class="hljs-bullet">-</span>   <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/psf/black</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">22.8</span><span class="hljs-number">.0</span>
    <span class="hljs-attr">hooks:</span>
    <span class="hljs-bullet">-</span>   <span class="hljs-attr">id:</span> <span class="hljs-string">black</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/pycqa/isort</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">5.13</span><span class="hljs-number">.2</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">isort</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/asottile/pyupgrade</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">v3.15.0</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">pyupgrade</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">"--py3-plus"</span>, <span class="hljs-string">"--py39-plus"</span>]
<span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/adamchainz/django-upgrade</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">"1.15.0"</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">django-upgrade</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">--target-version</span>, <span class="hljs-string">"4.0"</span>]
<span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/pycqa/bandit</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">1.7</span><span class="hljs-number">.7</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">bandit</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">"-iii"</span>, <span class="hljs-string">"-ll"</span>]
<span class="hljs-bullet">-</span> <span class="hljs-attr">repo:</span> <span class="hljs-string">https://github.com/charliermarsh/ruff-pre-commit</span>
    <span class="hljs-comment"># Ruff version.</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-string">"v0.2.1"</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">ruff</span>
</code></pre>
<h2 id="heading-bonus-ci-integration"><strong>Bonus: CI integration</strong></h2>
<p>To make sure no one skip the pre-commit setup, either by intention of if someone forget to install the hooks locally after cloning the project for the first time, you can run <code>pre-commit run --all</code> in your CI.</p>
<p>Caching might be tricky and useful as initial environment setup take some time and therefore you can use the <code>PRE_COMMIT_HOME</code> for specifying which directory is used for the cache</p>
<h3 id="heading-gitlab-ci">Gitlab CI</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">precommit:</span>
  <span class="hljs-attr">image:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">python:3.10</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">test</span>
  <span class="hljs-attr">variables:</span>
    <span class="hljs-attr">PRE_COMMIT_HOME:</span> <span class="hljs-string">".cache/.pre-commit-cache/"</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">pre-commit</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">pre-commit</span> <span class="hljs-string">run</span> <span class="hljs-string">--all</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">exists:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">".pre-commit-config.yaml"</span>
  <span class="hljs-attr">interruptible:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">cache:</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">$CI_PROJECT_NAME-precommit-cache-v1</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.cache/.pre-commit-cache/</span>
</code></pre>
<h3 id="heading-github-actions">Github Actions</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">create:</span>
    <span class="hljs-attr">tags:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"*"</span> <span class="hljs-comment"># run on all tags</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">python-version:</span> <span class="hljs-number">3.11</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">pre-commit</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">pre-commit</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Cache</span> <span class="hljs-string">pre-commit</span> <span class="hljs-string">hooks</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">pre-commit-cache</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/cache@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">$HOME/.cache/pre-commit</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-pre-commit-hooks-${{</span> <span class="hljs-string">hashFiles('**/.pre-commit-config.yaml')</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">restore-keys:</span> <span class="hljs-string">|
            ${{ runner.os }}-pre-commit-hooks-
</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">pre-commit</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
          <span class="hljs-string">pre-commit</span> <span class="hljs-string">run</span> <span class="hljs-string">--all-files</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Building API with Django rest framework]]></title><description><![CDATA[I will show how to create simple yet efficient APIs using Django REST framework. Starting with basic examples and progressing to more complex ones.
Project setup
To begin, we will use pipenv to set up our environment. First, we will clone the base re...]]></description><link>https://blog.mounirmesselmeni.de/building-api-with-django-rest-framework</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/building-api-with-django-rest-framework</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Python]]></category><category><![CDATA[pipenv]]></category><category><![CDATA[nested serializer]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Fri, 15 Dec 2017 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/hpjSkU2UYSU/upload/v1665904414531/e31qfYEFw.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I will show how to create simple yet efficient APIs using Django REST framework. Starting with basic examples and progressing to more complex ones.</p>
<h2 id="heading-project-setup">Project setup</h2>
<p>To begin, we will use pipenv to set up our environment. First, we will clone the base repository and then run a few additional commands. One of the benefits of using pipenv is that it automatically creates a virtual environment for us.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> -b minimal https://github.com/MounirMesselmeni/restframework-tutorial.git rest_tuto
<span class="hljs-built_in">cd</span> rest_tuto
<span class="hljs-comment"># installing pipenv as system package</span>
pip install pipenv
<span class="hljs-comment"># installing project dependencies, use --python 3.6 if it's installed</span>
pipenv install --python3
<span class="hljs-comment"># activating virtualenv via pipenv</span>
pipenv shell
<span class="hljs-comment"># run migration</span>
./manage.py migrate
<span class="hljs-comment"># create a superuser to play with your models via admin</span>
./manage.py createsuperuser
<span class="hljs-comment"># run django server</span>
./manage.py runserver
</code></pre>
<p>Now, you can examine the structure of the models in the <code>courses/models.py</code> file. Then, proceed to the admin area and add some data.</p>
<h2 id="heading-starting-the-api">Starting the API</h2>
<p>Now, let's create our first API endpoint and ensure it is functioning properly. To start, create a new Python module named <code>api</code>.</p>
<pre><code class="lang-bash">mkdir api
touch api/__init__.py
touch api/serializers.py
touch api/views.py
touch api/urls.py
</code></pre>
<p>Next, we will create a serializer for the Teacher model in "api/serializers.py". The serializer will instruct Django REST framework on how to present our data in various formats (such as HTML, JSON, or XML) and how to handle data parsing between these formats and Python.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> rest_framework <span class="hljs-keyword">import</span> serializers

<span class="hljs-keyword">from</span> courses.models <span class="hljs-keyword">import</span> Teacher


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Teacher
        fields = [
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'name'</span>,
            <span class="hljs-string">'speciality'</span>,
        ]
</code></pre>
<p>We will be using ModelSerializer which is quite similar to Django's ModelForm, it's a serializer for a model, that helps us to write less code and automatically generates fields based on the model fields including validation and field types.</p>
<p>Now, let's utilize Django REST framework viewsets. ViewSets allows us to perform CRUD operations using a single endpoint:</p>
<ul>
<li><p>Create via POST requests</p>
</li>
<li><p>Read via GET requests</p>
</li>
<li><p>Update via PUT requests</p>
</li>
<li><p>Delete via DELETE requests</p>
</li>
</ul>
<p>We can choose to disable some of them if not needed, but for now, we will use all of them.</p>
<p>Let's create our first viewset in <code>api/views.py</code>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> rest_framework <span class="hljs-keyword">import</span> viewsets

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> serializers
<span class="hljs-keyword">from</span> courses <span class="hljs-keyword">import</span> models <span class="hljs-keyword">as</span> courses_models


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherViewSet</span>(<span class="hljs-params">viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Teacher.objects.all()
    serializer_class = serializers.TeacherSerializer
</code></pre>
<p>We need to specify a queryset for the ViewSet, which will also be used for listing teachers. If we want to display only teachers with specific filters, we can make that change here.</p>
<p>Now, we will inform Django REST framework to publish our first endpoint or viewset. This can be done manually by adding the viewset to the <code>urls.py</code> file in the traditional Django way, but it's better to use Django REST framework routers for this. They are useful as they will automatically generate URLs with an ID parameter for retrieving a specific teacher, for example.</p>
<p>Let's add this to <code>api.urls.py</code>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.conf.urls <span class="hljs-keyword">import</span> url, include
<span class="hljs-keyword">from</span> rest_framework.routers <span class="hljs-keyword">import</span> DefaultRouter

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

router = DefaultRouter()
router.register(<span class="hljs-string">r'teachers'</span>, views.TeacherViewSet)

urlpatterns = [
    url(<span class="hljs-string">r'^'</span>, include(router.urls)),
    url(<span class="hljs-string">r'^api-auth/'</span>, include(<span class="hljs-string">'rest_framework.urls'</span>, namespace=<span class="hljs-string">'rest_framework'</span>))
]
</code></pre>
<p>Now we have to include api.urls inside our project main urls, <code>rest_tuto/urls.py</code></p>
<pre><code class="lang-plaintext">from django.contrib import admin
from django.urls import path, include
import api.urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(api.urls)),
]
</code></pre>
<p>The <code>api-auth</code> is used for API authentication, we will cover that later.</p>
<p>Now, you can open your browser and navigate to "<a target="_blank" href="http://localhost:8000/api/"><strong>http://localhost:8000/api/</strong></a>". The HTML displayed is generated by Django REST framework and lists all available endpoints. You're currently viewing the HTML format, but you can also view the JSON format by adding <code>?format=json</code> to the URL.</p>
<p>Go to the <code>teachers</code> API and experiment with it. Add some data using curl or the Django REST framework HTML form.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create teacher</span>
curl -d <span class="hljs-string">'{"name": "John", "speciality": "Physics"}'</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -X POST http://localhost:8000/api/teachers/

<span class="hljs-comment"># List teachers</span>
curl -X GET http://localhost:8000/api/teachers/

<span class="hljs-comment"># Get one teacher</span>
curl -X GET http://localhost:8000/api/teachers/1/

<span class="hljs-comment"># Delete teacher</span>
curl -X DELETE http://localhost:8000/api/teachers/1/

<span class="hljs-comment"># Create teacher another time</span>
curl -d <span class="hljs-string">'{"name": "John", "speciality": "Physics"}'</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -X POST http://localhost:8000/api/teachers/

<span class="hljs-comment"># Update one teacher</span>
curl -d <span class="hljs-string">'{"name": "John2", "speciality": "Physics"}'</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -X PUT http://localhost:8000/api/teachers/2/

<span class="hljs-comment"># Create teacher with missing data</span>
curl -d <span class="hljs-string">'{"speciality": "Physics"}'</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -X POST http://localhost:8000/api/teachers/
<span class="hljs-comment"># Will complain and show errors in json</span>
</code></pre>
<p>Now, let's explore relationships further, based on the models we have already set up.</p>
<h2 id="heading-custom-serializer-method">Custom serializer method</h2>
<p>t can be useful for example, for calculated fields. In the case of students, we have their birthdate and we want to return their calculated age for each student. To achieve this, we can use the Django REST framework serializer method field <code>SerializerMethodField</code>. The default method name will be <code>get_fieldname</code> where <code>fieldname</code> is the name of your field.</p>
<pre><code class="lang-python">
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudentSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    age = serializers.SerializerMethodField()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_age</span>(<span class="hljs-params">self, obj</span>):</span>
        today = datetime.date.today()
        <span class="hljs-keyword">return</span> (
            today.year - obj.birth_date.year - ((today.month, today.day) &lt; (obj.birth_date.month,
                                                                            obj.birth_date.day))
        )
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Student
        fields = [
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'name'</span>,
            <span class="hljs-string">'birth_date'</span>,
            <span class="hljs-string">'current_level'</span>,
            <span class="hljs-string">'age'</span>,
        ]
</code></pre>
<p>Note that obj will be an instance of <code>Student</code> model.</p>
<h2 id="heading-relationships">Relationships</h2>
<p>By default, Django REST framework will display related fields as IDs. If you want to include the entire related object, you will need to specify which serializer should be used. In the case of many-to-many relationships, you must pass the serializer with "many=True"</p>
<p>Let's take a look at how the "CourseSerializer" should be implemented:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CourseSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    teacher = TeacherSerializer()
    students = StudentSerializer(many=<span class="hljs-literal">True</span>, read_only=<span class="hljs-literal">True</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Course
        fields = [
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'name'</span>,
            <span class="hljs-string">'teacher'</span>,
            <span class="hljs-string">'level'</span>,
            <span class="hljs-string">'students'</span>,
        ]
</code></pre>
<p>We used <code>read_only=True</code> to tell drf to use that field only while reading data, for creation this field will be ignored, if disabled drf will raise an exception when trying to post data complaining that you should deal on your own with m2m fields in the <code>perfom_create</code> method.</p>
<p>Here an overview of how api app looks like right now, as it's time to play around with it.</p>
<p><code>api/serializers.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> rest_framework <span class="hljs-keyword">import</span> serializers

<span class="hljs-keyword">from</span> courses.models <span class="hljs-keyword">import</span> Teacher, Student, Course


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Teacher
        fields = [
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'name'</span>,
            <span class="hljs-string">'speciality'</span>,
        ]


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudentSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    age = serializers.SerializerMethodField()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_age</span>(<span class="hljs-params">self, obj</span>):</span>
        today = datetime.date.today()
        <span class="hljs-keyword">return</span> (
            today.year - obj.birth_date.year - ((today.month, today.day) &lt; (obj.birth_date.month,
                                                                            obj.birth_date.day))
        )
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Student
        fields = [
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'name'</span>,
            <span class="hljs-string">'birth_date'</span>,
            <span class="hljs-string">'current_level'</span>,
            <span class="hljs-string">'age'</span>,
        ]


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CourseSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    teacher = TeacherSerializer()
    students = StudentSerializer(many=<span class="hljs-literal">True</span>, read_only=<span class="hljs-literal">True</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Course
        fields = [
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'name'</span>,
            <span class="hljs-string">'teacher'</span>,
            <span class="hljs-string">'level'</span>,
            <span class="hljs-string">'students'</span>,
        ]
</code></pre>
<p><code>api/views.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> rest_framework <span class="hljs-keyword">import</span> viewsets

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> serializers
<span class="hljs-keyword">from</span> courses <span class="hljs-keyword">import</span> models <span class="hljs-keyword">as</span> courses_models


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherViewSet</span>(<span class="hljs-params">viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Teacher.objects.all()
    serializer_class = serializers.TeacherSerializer


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudentViewSet</span>(<span class="hljs-params">viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Student.objects.all()
    serializer_class = serializers.StudentSerializer


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CourseViewSet</span>(<span class="hljs-params">viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Course.objects.all()
    serializer_class = serializers.CourseSerializer
</code></pre>
<p><code>api/urls.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.conf.urls <span class="hljs-keyword">import</span> url, include
<span class="hljs-keyword">from</span> rest_framework.routers <span class="hljs-keyword">import</span> DefaultRouter

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

router = DefaultRouter()
router.register(<span class="hljs-string">r'teachers'</span>, views.TeacherViewSet)
router.register(<span class="hljs-string">r'students'</span>, views.StudentViewSet)
router.register(<span class="hljs-string">r'courses'</span>, views.CourseViewSet)

urlpatterns = [
    url(<span class="hljs-string">r'^'</span>, include(router.urls)),
    url(<span class="hljs-string">r'^api-auth/'</span>, include(<span class="hljs-string">'rest_framework.urls'</span>, namespace=<span class="hljs-string">'rest_framework'</span>))
]
</code></pre>
<h2 id="heading-filtering">Filtering</h2>
<p>To help with quick filtering implementation, we will use the django-filter package. It's already installed, but let's add it to our <code>INSTALLED_APPS</code>.</p>
<pre><code class="lang-python">INSTALLED_APPS = [
    ...
    <span class="hljs-string">'django_filters'</span>,
]
</code></pre>
<p>Now, we can configure Django REST framework to use django-filter as our global filter backend. This can also be done on a per-view level. Add the following to your <code>settings.py</code> file.</p>
<pre><code class="lang-python">REST_FRAMEWORK = {
    <span class="hljs-string">'DEFAULT_FILTER_BACKENDS'</span>: (
        <span class="hljs-string">'django_filters.rest_framework.DjangoFilterBackend'</span>,
    ),
}
</code></pre>
<p>Create a <code>api/filters.py</code> where we will add filtering for the teacher endpoint</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> django_filters
<span class="hljs-keyword">from</span> django_filters <span class="hljs-keyword">import</span> rest_framework <span class="hljs-keyword">as</span> filters

<span class="hljs-keyword">from</span> courses <span class="hljs-keyword">import</span> models 

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherFilter</span>(<span class="hljs-params">filters.FilterSet</span>):</span>
    name = django_filters.CharFilter(lookup_expr=<span class="hljs-string">'icontains'</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = models.Teacher
        fields = [<span class="hljs-string">'name'</span>]
</code></pre>
<p>The last thing we need is to specify the <code>filter_class</code> in the <code>TeacherViewSet</code></p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherViewSet</span>(<span class="hljs-params">viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Teacher.objects.all()
    serializer_class = serializers.TeacherSerializer
    filter_class = filters.TeacherFilter
</code></pre>
<p>The <code>lookup_expr</code> setting specifies to django-filter which Django ORM lookup it will use. Let's test it out using curl and see the results.</p>
<pre><code class="lang-bash">curl -X GET http://localhost:8000/api/teachers/\?name\=john
</code></pre>
<p>Now, we want to filter for students based on their age, we will need to use a custom method for filtering as we will calculate the right year to look for. To calculate the difference in years, we will need to install python-dateutil.</p>
<pre><code class="lang-bash">pipenv install python-dateutil
</code></pre>
<p>Then modify our <code>filters.py</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> dateutil.relativedelta <span class="hljs-keyword">import</span> relativedelta
<span class="hljs-keyword">import</span> django_filters
<span class="hljs-keyword">from</span> django_filters <span class="hljs-keyword">import</span> rest_framework <span class="hljs-keyword">as</span> filters

<span class="hljs-keyword">from</span> courses <span class="hljs-keyword">import</span> models 

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherFilter</span>(<span class="hljs-params">filters.FilterSet</span>):</span>
    name = django_filters.CharFilter(lookup_expr=<span class="hljs-string">'icontains'</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = models.Teacher
        fields = [<span class="hljs-string">'name'</span>]


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudentFilter</span>(<span class="hljs-params">filters.FilterSet</span>):</span>
    age = django_filters.NumberFilter(method=<span class="hljs-string">'age_filter'</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">age_filter</span>(<span class="hljs-params">self, queryset, name, value</span>):</span>
        today = datetime.date.today()
        age = (today - relativedelta(years=value)).year
        <span class="hljs-keyword">return</span> queryset.filter(birth_date__year=age)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = models.Student
        fields = [<span class="hljs-string">'age'</span>]
</code></pre>
<p>Let's try it out, maybe you will need to add some students, via curl, drf forms, Django admin or Django shell</p>
<pre><code class="lang-bash">curl -X GET http://localhost:8000/api/students/?age=20
</code></pre>
<h2 id="heading-pagination">Pagination</h2>
<p>Configure <a target="_blank" href="http://www.django-rest-framework.org/api-guide/pagination/">pagination</a> in <code>settings.py</code>, pagination can help when dealing with a lot of data, imagine we have a list of 10000 students, is will not be good to return this big list, we can return 100 per 100.</p>
<p>Be careful as pagination will modify the returned format:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"count"</span>: <span class="hljs-number">1023</span>
    <span class="hljs-string">"next"</span>: <span class="hljs-string">"https://api.example.org/accounts/?limit=100&amp;offset=500"</span>,
    <span class="hljs-attr">"previous"</span>: <span class="hljs-string">"https://api.example.org/accounts/?limit=100&amp;offset=300"</span>,
    <span class="hljs-attr">"results"</span>: [
       …
    ]
}
</code></pre>
<pre><code class="lang-python">REST_FRAMEWORK = {
    <span class="hljs-string">'DEFAULT_FILTER_BACKENDS'</span>: (<span class="hljs-string">'django_filters.rest_framework.DjangoFilterBackend'</span>,),
    <span class="hljs-string">'DEFAULT_PAGINATION_CLASS'</span>: <span class="hljs-string">'rest_framework.pagination.LimitOffsetPagination'</span>,
    <span class="hljs-string">'PAGE_SIZE'</span>: <span class="hljs-number">100</span>,
}
</code></pre>
<h2 id="heading-authentication">Authentication</h2>
<p>It's best to use a session if you would like to use DRF build in HTML for testing and trying out the API endpoints. There are many authentication backends supported by DRF, there are also some other third-party apps that provide more backends like JWT (<a target="_blank" href="https://github.com/GetBlimp/django-rest-framework-jwt">You can check</a>)</p>
<p>We will now configure session and token authentication, let's configure DRF in <code>settings.py</code></p>
<pre><code class="lang-python">REST_FRAMEWORK = {
    <span class="hljs-string">'DEFAULT_FILTER_BACKENDS'</span>: (<span class="hljs-string">'django_filters.rest_framework.DjangoFilterBackend'</span>,),
    <span class="hljs-string">'DEFAULT_PAGINATION_CLASS'</span>: <span class="hljs-string">'rest_framework.pagination.LimitOffsetPagination'</span>,
    <span class="hljs-string">'PAGE_SIZE'</span>: <span class="hljs-number">100</span>,
    <span class="hljs-string">'DEFAULT_AUTHENTICATION_CLASSES'</span>: (
        <span class="hljs-string">'rest_framework.authentication.SessionAuthentication'</span>,
        <span class="hljs-string">'rest_framework.authentication.TokenAuthentication'</span>,
    ),
}
</code></pre>
<p>Now we have to add DRF auth app to <code>INSTALLED_APPS</code></p>
<pre><code class="lang-python">INSTALLED_APPS = (
    ...
    <span class="hljs-string">'rest_framework.authtoken'</span>,
)
</code></pre>
<p>We can also expose an obtain token endpoint, where users can send a username/password and have their token back. To do so we have to modify <code>api/urls.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.conf.urls <span class="hljs-keyword">import</span> url, include
<span class="hljs-keyword">from</span> rest_framework.routers <span class="hljs-keyword">import</span> DefaultRouter
<span class="hljs-keyword">from</span> rest_framework.authtoken.views <span class="hljs-keyword">import</span> obtain_auth_token

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

router = DefaultRouter()
router.register(<span class="hljs-string">r'teachers'</span>, views.TeacherViewSet)
router.register(<span class="hljs-string">r'students'</span>, views.StudentViewSet)
router.register(<span class="hljs-string">r'courses'</span>, views.CourseViewSet)

urlpatterns = [
    url(<span class="hljs-string">r'^'</span>, include(router.urls)),
    url(<span class="hljs-string">r'^api-auth/'</span>, include(<span class="hljs-string">'rest_framework.urls'</span>, namespace=<span class="hljs-string">'rest_framework'</span>)),
    url(<span class="hljs-string">r'^api-token-auth/'</span>, obtain_auth_token),
]
</code></pre>
<p>Now we have to run migrate to create token app tables:</p>
<pre><code class="lang-shell">./manage.py migrate
</code></pre>
<p>Let's get the token via the obtain token view:</p>
<pre><code class="lang-shell">curl -X POST http://localhost:8000/api/api-token-auth/ \
-H 'Content-type: application/json' \
-d '{"username": "admin", "password": "yourAdminPassword"}'

{"token":"c98d345c4eeeb5529e184526b4f3effbc8957251"}%
</code></pre>
<p>We can now turn on permission, we can set up permission per view/viewset level or globally via <code>settings.py</code></p>
<pre><code class="lang-python">REST_FRAMEWORK = {
    <span class="hljs-string">'DEFAULT_FILTER_BACKENDS'</span>: (<span class="hljs-string">'django_filters.rest_framework.DjangoFilterBackend'</span>,),
    <span class="hljs-string">'DEFAULT_PAGINATION_CLASS'</span>: <span class="hljs-string">'rest_framework.pagination.LimitOffsetPagination'</span>,
    <span class="hljs-string">'PAGE_SIZE'</span>: <span class="hljs-number">100</span>,
    <span class="hljs-string">'DEFAULT_AUTHENTICATION_CLASSES'</span>: (
        <span class="hljs-string">'rest_framework.authentication.SessionAuthentication'</span>,
        <span class="hljs-string">'rest_framework.authentication.TokenAuthentication'</span>,
    ),
    <span class="hljs-string">'DEFAULT_PERMISSION_CLASSES'</span>: (<span class="hljs-string">'rest_framework.permissions.IsAuthenticated'</span>,),
}
</code></pre>
<p>We still can override permission per view level if we want. Let's see if the curl call will return a 403 status code</p>
<pre><code class="lang-shell">curl -X GET http://localhost:8000/api/teachers/\?name\=john                                                                        
{"detail":"Authentication credentials were not provided."}%
</code></pre>
<p>Now we should provide the token which we got as header: <code>Authorization: Token c98d345c4eeeb5529e184526b4f3effbc8957251</code> Do not forget to replace c98d345c4eeeb5529e184526b4f3effbc8957251 by your local token</p>
<pre><code class="lang-shell">curl -H 'Authorization: Token c98d345c4eeeb5529e184526b4f3effbc8957251' -X GET http://localhost:8000/api/teachers/\?name\=john

{"count":4,"next":null,"previous":null,"results":[{"id":2,"name":"John2","speciality":"Physics"},{"id":3,"name":"John","speciality":"Physics"},{"id":4,"name":"John","speciality":"Physics"},{"id":5,"name":"John","speciality":"Physics"}]}%
</code></pre>
<h2 id="heading-nested-api">Nested API</h2>
<p>We will use drf-extension to add nested support. We will be able to list all courses of a teacher directly via the teacher detail endpoint /api/teacher/1/courses/ I'm not using this too often as I prefer to use flat endpoints, but sometime it may be requested and I will show how it's possible and easy.</p>
<p>We will install <a target="_blank" href="https://github.com/chibisov/drf-extensions">drf-extensions</a> which have a bunch of helpers, we will need right now the nested router.</p>
<pre><code class="lang-shell">pipenv install git+https://github.com/chibisov/drf-extensions.git@master#egg=drf_extensions
</code></pre>
<p>PS: Until the time of writing this, drf-extension official release does not support Django 2.0. that's why we are using the master branch directly from Github. You may not need to use git link as soon as a new version will be released (&gt; 0.3.1)</p>
<p>Let's modify <code>api.urls.py</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.conf.urls <span class="hljs-keyword">import</span> url, include
<span class="hljs-keyword">from</span> rest_framework_extensions.routers <span class="hljs-keyword">import</span> ExtendedDefaultRouter
<span class="hljs-keyword">from</span> rest_framework.authtoken.views <span class="hljs-keyword">import</span> obtain_auth_token

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

router = ExtendedDefaultRouter()
router.register(<span class="hljs-string">r'teachers'</span>, views.TeacherViewSet).register(
    <span class="hljs-string">r'courses'</span>, views.CourseViewSet, base_name=<span class="hljs-string">'courses'</span>, parents_query_lookups=[<span class="hljs-string">'teacher'</span>],
)
router.register(<span class="hljs-string">r'students'</span>, views.StudentViewSet)

urlpatterns = [
    url(<span class="hljs-string">r'^'</span>, include(router.urls)),
    url(<span class="hljs-string">r'^api-auth/'</span>, include(<span class="hljs-string">'rest_framework.urls'</span>, namespace=<span class="hljs-string">'rest_framework'</span>)),
    url(<span class="hljs-string">r'^api-token-auth/'</span>, obtain_auth_token),
]
</code></pre>
<p>As you see, we can chain router registrations, keep in mind this is only supported for reading, for creation you will need to do a bit more to get for example the current teacher while creating a new course.</p>
<p>And then <code>api/views/py</code> to add <code>NestedViewSetMixin</code> to our viewsets</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> rest_framework <span class="hljs-keyword">import</span> viewsets
<span class="hljs-keyword">from</span> rest_framework_extensions.mixins <span class="hljs-keyword">import</span> NestedViewSetMixin

<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> serializers
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> filters
<span class="hljs-keyword">from</span> courses <span class="hljs-keyword">import</span> models <span class="hljs-keyword">as</span> courses_models


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherViewSet</span>(<span class="hljs-params">NestedViewSetMixin, viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Teacher.objects.all()
    serializer_class = serializers.TeacherSerializer
    filter_class = filters.TeacherFilter


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudentViewSet</span>(<span class="hljs-params">NestedViewSetMixin, viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Student.objects.all()
    serializer_class = serializers.StudentSerializer
    filter_class = filters.StudentFilter


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CourseViewSet</span>(<span class="hljs-params">NestedViewSetMixin, viewsets.ModelViewSet</span>):</span>
    queryset = courses_models.Course.objects.all()
    serializer_class = serializers.CourseSerializer
</code></pre>
<p>Now you can try <code>/api/teachers/1/courses/</code></p>
<pre><code class="lang-shell">curl -H 'Authorization: Token c98d345c4eeeb5529e184526b4f3effbc8957251' -X GET http://localhost:8000/api/teachers/1/courses/

{"count":1,"next":null,"previous":null,"results":[{"id":1,"name":"c1","teacher":{"id":1,"name":"Mounir Messelmeni","speciality":"Python"},"level":2,"students":[{"id":1,"name":"John Wick","birth_date":"1990-02-14","current_level":3,"age":27},{"id":2,"name":"JN","birth_date":"2017-12-15","current_level":2,"age":0}]}]}%
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Ordering/Sorting Python lists]]></title><description><![CDATA[Python is an easy, readable and simple language, many new python programmer will not know how to order/sort a python list.
I will demonstrate some simple examples on how to order different type of list. You will realise how simple it's.
A list of int...]]></description><link>https://blog.mounirmesselmeni.de/orderingsorting-python-lists</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/orderingsorting-python-lists</guid><category><![CDATA[Python]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Tue, 28 Feb 2017 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/0gkw_9fy0eQ/upload/v1665904473495/xCWu2wj8m.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Python is an easy, readable and simple language, many new python programmer will not know how to order/sort a python list.</p>
<p>I will demonstrate some simple examples on how to order different type of list. You will realise how simple it's.</p>
<h2 id="heading-a-list-of-integer">A list of integer</h2>
<p>There are two ways to do so, using the <a target="_blank" href="https://docs.python.org/3.6/library/stdtypes.html?highlight=list.sort#list.sort">sort method of the list</a> object or using python builtin method <a target="_blank" href="https://docs.python.org/3.6/library/functions.html#sorteds">sorted</a>. The main difference is that the list's sort method will change the list, but sorted will creates a new list from the old and returns the new one will.</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: my_list1 = [<span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">0</span>, <span class="hljs-number">3</span>, <span class="hljs-number">7</span>, <span class="hljs-number">2</span>]

  In [<span class="hljs-number">2</span>]: my_list1.sort()

  In [<span class="hljs-number">3</span>]: my_list1
  Out[<span class="hljs-number">3</span>]: [<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>]

  In [<span class="hljs-number">4</span>]: my_list2 = [<span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">0</span>, <span class="hljs-number">3</span>, <span class="hljs-number">7</span>, <span class="hljs-number">2</span>]

  In [<span class="hljs-number">5</span>]: sorted(my_list2)
  Out[<span class="hljs-number">5</span>]: [<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>]

  In [<span class="hljs-number">6</span>]: my_list2
  Out[<span class="hljs-number">6</span>]: [<span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">0</span>, <span class="hljs-number">3</span>, <span class="hljs-number">7</span>, <span class="hljs-number">2</span>]
</code></pre>
<h2 id="heading-a-list-of-characters-or-strings">A list of characters or strings</h2>
<p>This apply the same to a list of strings or characters which will be ordered by the ascii code of the first characters. But pay attention that it will be case-sensitive sorting, to do so we have to use the key parameter to define what the sorting should be based on.</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: my_list1 = [<span class="hljs-string">'z'</span>, <span class="hljs-string">'Da'</span>, <span class="hljs-string">'dC'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'a'</span>]

  In [<span class="hljs-number">2</span>]: my_list1.sort()

  In [<span class="hljs-number">3</span>]: my_list1
  Out[<span class="hljs-number">3</span>]: [<span class="hljs-string">'Da'</span>, <span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'dC'</span>, <span class="hljs-string">'z'</span>]

  In [<span class="hljs-number">4</span>]: my_list1.sort(key=str.lower)

  In [<span class="hljs-number">5</span>]: my_list1
  Out[<span class="hljs-number">5</span>]: [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'Da'</span>, <span class="hljs-string">'dC'</span>, <span class="hljs-string">'z'</span>]

  In [<span class="hljs-number">6</span>]: my_list2 = [<span class="hljs-string">'z'</span>, <span class="hljs-string">'Da'</span>, <span class="hljs-string">'dC'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'a'</span>]

  In [<span class="hljs-number">7</span>]: sorted(my_list2, key=str.lower)
  Out[<span class="hljs-number">7</span>]: [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'Da'</span>, <span class="hljs-string">'dC'</span>, <span class="hljs-string">'z'</span>]
</code></pre>
<h2 id="heading-a-list-of-dictionnary-based-on-value">A list of dictionnary based on value</h2>
<p>Here we need to use <a target="_blank" href="https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression">lambda functions</a>, there are some other way like using <a target="_blank" href="https://docs.python.org/3/library/operator.html#operator.attrgetter">attrgetter</a> from the builtin operator module from python but I prefer lambda as they are inline and more readable (in this case)</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: my_list1 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">2</span>]: my_list1.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">'age'</span>])

  In [<span class="hljs-number">3</span>]: my_list1
  Out[<span class="hljs-number">3</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>}]

  In [<span class="hljs-number">4</span>]: my_list2 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">5</span>]: sorted(my_list2, key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">'age'</span>])
  Out[<span class="hljs-number">5</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>}]
</code></pre>
<h2 id="heading-a-list-of-object-based-on-some-attribute">A list of object based on some attribute</h2>
<p>Here we need can also to use <a target="_blank" href="https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression">lambda functions</a>, it's very similar to the dictionary ordering but instead of accessing the dictionary per key we will access the object attribute</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>(<span class="hljs-params">object</span>):</span>
     ...:     <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, name, age</span>):</span>
     ...:         self.name = name
     ...:         self.age = age

  In [<span class="hljs-number">2</span>]: my_list1 = [Person(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), Person(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), Person(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">3</span>]: my_list1.sort(key=<span class="hljs-keyword">lambda</span> x: x.age)

  In [<span class="hljs-number">4</span>]: my_list1
  Out[<span class="hljs-number">4</span>]: 
  [&lt;__main__.Person at <span class="hljs-number">0x7fa49abdaf50</span>&gt;,
   &lt;__main__.Person at <span class="hljs-number">0x7fa49abda590</span>&gt;,
   &lt;__main__.Person at <span class="hljs-number">0x7fa49abda250</span>&gt;]
</code></pre>
<h2 id="heading-reverse-a-list-or-order-a-list-in-a-descending-order">Reverse a list or order a list in a descending order</h2>
<p>Both of the method we used (list.sort and sorted) support a boolean parameter called reverse, when set to <code>True</code> the order will be descending.</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: my_list1 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">2</span>]: my_list1.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">'age'</span>], reverse=<span class="hljs-literal">True</span>)

  In [<span class="hljs-number">3</span>]: my_list1
  Out[<span class="hljs-number">3</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>}]

  In [<span class="hljs-number">4</span>]: my_list2 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">5</span>]: sorted(my_list2, key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">'age'</span>], reverse=<span class="hljs-literal">True</span>)
  Out[<span class="hljs-number">5</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>}]
</code></pre>
<h2 id="heading-reverse-a-list-without-sorting-it">Reverse a list without sorting it</h2>
<p>We have the same logic between <a target="_blank" href="https://docs.python.org/3.6/library/stdtypes.html?highlight=list.sort#list.sort">list.sort</a> and <a target="_blank" href="https://docs.python.org/3.6/library/functions.html#sorteds">sorted</a> as for list.reverse and reversed, <strong>EXCEPT that reversed return a listreverseiterator object instead of a list</strong>.</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: my_list1 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">2</span>]: my_list1.reverse()

  In [<span class="hljs-number">3</span>]: my_list1
  Out[<span class="hljs-number">3</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>}]

  In [<span class="hljs-number">4</span>]: my_list2 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]

  In [<span class="hljs-number">5</span>]: list(reversed(my_list2))
  Out[<span class="hljs-number">5</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>}]

  In [<span class="hljs-number">6</span>]: my_list2
  Out[<span class="hljs-number">6</span>]: 
  [{<span class="hljs-string">'age'</span>: <span class="hljs-number">25</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Tom'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">50</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'John'</span>},
   {<span class="hljs-string">'age'</span>: <span class="hljs-number">20</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Anna'</span>}]
</code></pre>
<p>I used casted the return of the reversed as I mentioned above that it's returned result is not a list but an iterator. I recommend to cast the return of reversed if you will not just iterate over it or to use list comprehension.</p>
<pre><code class="lang-python">
  In [<span class="hljs-number">1</span>]: my_list2 = [dict(name=<span class="hljs-string">'Tom'</span>, age=<span class="hljs-number">25</span>), dict(name=<span class="hljs-string">'John'</span>, age=<span class="hljs-number">50</span>), dict(name=<span class="hljs-string">'Anna'</span>, age=<span class="hljs-number">20</span>)]
  In [<span class="hljs-number">2</span>]: reversed(my_list2)
  Out[<span class="hljs-number">3</span>]: &lt;listreverseiterator at <span class="hljs-number">0x7fe9508b5b50</span>&gt;
</code></pre>
<p>If you are new to Python and you don't know about such kind of usage, I believe you will wonder how great it's to use such a cool language. All the examples were simple and there are plenty of other ways to do each of them, some could be more efficient regarding performance when dealing with larger list's elements and some could be more readable, I took the most simple ways I'm aware of.</p>
]]></content:encoded></item><item><title><![CDATA[Accessing prefetched objects inside your model's methods - Django ORM]]></title><description><![CDATA[I had many time the need to optimize some performance issues with Django ORM queries, then many times the prefetch_related and select_related were my best tool for this problem.
But when calling some business logic which reside in some model's method...]]></description><link>https://blog.mounirmesselmeni.de/accessing-prefetched-objects-inside-your-models-methods-django-orm</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/accessing-prefetched-objects-inside-your-models-methods-django-orm</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Mon, 06 Feb 2017 22:45:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/feXpdV001o4/upload/v1662328529946/lsqRkUWJ5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I had many time the need to optimize some performance issues with Django ORM queries, then many times the <a target="_blank" href="https://docs.djangoproject.com/en/1.10/ref/models/querysets/#prefetch-related">prefetch_related</a> and <a target="_blank" href="https://docs.djangoproject.com/en/1.10/ref/models/querysets/#select-related">select_related</a> were my best tool for this problem.
But when calling some business logic which reside in some model's methods which try to calculate or to fetch some related model data the optimization will not have any effect as these method are using <code>filter</code> or some queryset aggregations <code>Sum</code> or <code>Count</code> ...</p>
<p>The solution to this problem is either to redefine the logic to use prefetched data or more clean to make these methods now if there already prefetched objects, that's the best solution in my cases:</p>
<p>A basic models example <code>models.py</code>:</p>
<pre><code class="lang-python">
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Teacher</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">students_count</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> self.student_set.filter(age__gte=<span class="hljs-number">20</span>).count()

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)
        teacher = models.ForeignKey(Teacher)
        age = models.IntegerField(default=<span class="hljs-number">20</span>)
</code></pre>
<pre><code class="lang-python">
    <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> Teacher.objects.all():
        print(teacher.students_count())
    <span class="hljs-comment"># This will results into multiple DB queries equal to the number of teachers + 1</span>
    <span class="hljs-comment"># If we have 100 teachers in the DB, this will be causing 101 queries.</span>
</code></pre>
<p>Let's use <code>prefetch_related</code>:</p>
<pre><code class="lang-python">
    <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> Teacher.objects.all().prefetch_related(<span class="hljs-string">'student_set'</span>):
        print(teacher.students_count())
    <span class="hljs-comment"># Our prefetch have no effect as the method students_count is using filter which</span>
    <span class="hljs-comment"># will ignore the prefetched objects</span>
</code></pre>
<p>We have to fix the method <code>students_count</code> to consider checking for prefetched objects:</p>
<pre><code class="lang-python">
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Teacher</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">students_count</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">if</span> hasattr(self, <span class="hljs-string">'_prefetched_objects_cache'</span>) <span class="hljs-keyword">and</span> <span class="hljs-string">'student'</span> <span class="hljs-keyword">in</span> self._prefetched_objects_cache:
                <span class="hljs-keyword">return</span> len([x <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> self.student_set.all() <span class="hljs-keyword">if</span> x.age &gt;= <span class="hljs-number">20</span>])
            <span class="hljs-keyword">return</span> self.student_set.filter(age__gte=<span class="hljs-number">20</span>).count()
</code></pre>
<p>This way even calling <code>students_count</code> in a single instance without calling <code>prefetch_related</code> on the queryset will use the filter instead of the prefetched.</p>
]]></content:encoded></item><item><title><![CDATA[Accessing prefetched objects inside your model's methods]]></title><description><![CDATA[I had many time the need to optimise some performance issues with some ORM queries, then many times the prefetch_related and select_related were my best friends for this topic. But when calling some business logic which reside in some model's methods...]]></description><link>https://blog.mounirmesselmeni.de/accessing-prefetched-objects-inside-your-models-methods</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/accessing-prefetched-objects-inside-your-models-methods</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sun, 05 Feb 2017 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/wunVFNvqhfE/upload/v1665904533352/zAqHdCQ-K.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I had many time the need to optimise some performance issues with some ORM queries, then many times the <a target="_blank" href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#prefetch-related">prefetch_related</a> and <a target="_blank" href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#select-related">select_related</a> were my best friends for this topic. But when calling some business logic which reside in some model's methods which try to calculate or to fetch some related model data the optimisation will not have any effect as these method are using <code>filter</code> or some queryset aggregations <code>Sum</code> or <code>Count</code> ...</p>
<p>The solution to this problem is either to redefine the logic to use prefetched data or more clean to make these methods now if there already pre-feteched objects, that's the best solution in my cases:</p>
<p>A basic models example <code>models.py</code>:</p>
<pre><code class="lang-python">
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Teacher</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">students_count</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> self.student_set.filter(age__gte=<span class="hljs-number">20</span>).count()

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)
        teacher = models.ForeignKey(Teacher)
        age = models.IntegerField(default=<span class="hljs-number">20</span>)
</code></pre>
<pre><code class="lang-python">
    <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> Teacher.objects.all():
        print(teacher.students_count())
    <span class="hljs-comment"># This will results to the number of teachers + 1 SQL queries</span>
</code></pre>
<p>Let's use <code>prefetch_related</code>:</p>
<pre><code class="lang-python">
    <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> Teacher.objects.all().prefetch_related(<span class="hljs-string">'student_set'</span>):
        print(teacher.students_count())
    <span class="hljs-comment"># Our prefetch have no effect as the method students_count is using filter which</span>
    <span class="hljs-comment"># will ignore the prefetched objects</span>
</code></pre>
<p>We have to fix the method <code>students_count</code> to consider checking for prefetched objects:</p>
<pre><code class="lang-python">
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Teacher</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">students_count</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">if</span> hasattr(self, <span class="hljs-string">'_prefetched_objects_cache'</span>) <span class="hljs-keyword">and</span> <span class="hljs-string">'student'</span> <span class="hljs-keyword">in</span> self._prefetched_objects_cache:
                <span class="hljs-keyword">return</span> len([x <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> self.student_set.all() <span class="hljs-keyword">if</span> x.age &gt;= <span class="hljs-number">20</span>])
            <span class="hljs-keyword">return</span> self.student_set.filter(age__gte=<span class="hljs-number">20</span>).count()
</code></pre>
<p>This way even calling <code>students_count</code> in a single instance without calling <code>prefetch_related</code> on the queryset will use the filter instead of the prefetched, this can be useful in many cases but I better to use this only when you need to.</p>
<p>Thanks for reading</p>
]]></content:encoded></item><item><title><![CDATA[How to order a calculated count field in Django's admin]]></title><description><![CDATA[You can easily add a field which count a specific related model from a reverse ForeignKey relation or 
a ManyToMany relation, but ordering this field could not be easy from the first time.
Here we will see how to use annotate to add this field and ho...]]></description><link>https://blog.mounirmesselmeni.de/how-to-order-a-calculated-count-field-in-djangos-admin</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/how-to-order-a-calculated-count-field-in-djangos-admin</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sun, 20 Mar 2016 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/K-ZsC7YdJ6Y/upload/v1665904575343/Nqoro0hg0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You can easily add a field which count a specific related model from a reverse <code>ForeignKey</code> relation or 
a <code>ManyToMany</code> relation, but ordering this field could not be easy from the first time.</p>
<p>Here we will see how to use annotate to add this field and how to order it. You only have to define <code>admin_order_field</code>
as in the following.</p>
<p>A basic models example <code>models.py</code>:</p>
<pre><code class="lang-python">
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Teacher</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span>(<span class="hljs-params">models.Model</span>):</span>
        name = models.CharField(max_length=<span class="hljs-number">255</span>)
        teacher = models.ForeignKey(Teacher)
</code></pre>
<p>And the <code>admin.py</code></p>
<pre><code class="lang-python">
<span class="hljs-meta">    @admin.register(Teacher)</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
        list_display = (
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'students_count'</span>,
        )

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
            <span class="hljs-keyword">return</span> Teacher.objects.annotate(students_count=Count(<span class="hljs-string">'student'</span>))

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">students_count</span>(<span class="hljs-params">self, obj</span>):</span>
            <span class="hljs-keyword">return</span> obj.students_count

        students_count.short_description = _(<span class="hljs-string">'Students count'</span>)
        students_count.admin_order_field = <span class="hljs-string">'students_count'</span>
</code></pre>
<p>UPDATE (20/10/2022): With Django &gt;= 3.2 you can use the new <a target="_blank" href="https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#the-display-decorator">display decorator</a></p>
<pre><code class="lang-python"><span class="hljs-meta">    @admin.register(Teacher)</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeacherAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
        list_display = (
            <span class="hljs-string">'id'</span>,
            <span class="hljs-string">'students_count'</span>,
        )

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
            <span class="hljs-keyword">return</span> Teacher.objects.annotate(students_count=Count(<span class="hljs-string">'student'</span>))

<span class="hljs-meta">        @admin.display(</span>
            ordering=<span class="hljs-string">'students_count'</span>,
            description=_(<span class="hljs-string">'Students count'</span>),
        )
        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">students_count</span>(<span class="hljs-params">self, obj</span>):</span>
            <span class="hljs-keyword">return</span> obj.students_count
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Create django grouped select field for ModelChoiceField or ModelMultipleChoiceField]]></title><description><![CDATA[Django forms's builtin fields like ChoiceField, ModelChoiceField use by default forms.Select widget, but what we do if we need a Select with grouped options.
I found this awesome snippets that's helped me to achieve this, just in my case I needed to ...]]></description><link>https://blog.mounirmesselmeni.de/create-django-grouped-select-field-for-modelchoicefield-or-modelmultiplechoicefield</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/create-django-grouped-select-field-for-modelchoicefield-or-modelmultiplechoicefield</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sun, 24 Nov 2013 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/BI465ksrlWs/upload/v1665904603249/DeUFqrJEQ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Django forms's builtin fields like ChoiceField, ModelChoiceField use by default <code>forms.Select</code> widget, but what we do if we need a Select with grouped options.</p>
<p>I found this awesome <a target="_blank" href="https://djangosnippets.org/snippets/1968/">snippets</a> that's helped me to achieve this, just in my case I needed to add a line of code to show only options with parent.</p>
<h2 id="heading-example">Example</h2>
<p>Here's a model example:</p>
<pre><code class="lang-python">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Category</span>(<span class="hljs-params">models.Model</span>):</span>

        name = models.CharField(max_length=<span class="hljs-number">100</span>)
        parent = models.ForeignKey(<span class="hljs-string">'self'</span>, null=<span class="hljs-literal">True</span>, blank=<span class="hljs-literal">True</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__unicode__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> <span class="hljs-string">u'{0}'</span>.format(self.name)
</code></pre>
<p>I created a form that use this model:</p>
<pre><code class="lang-python">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleForm</span>(<span class="hljs-params">forms.Form</span>):</span>

        category = GroupedModelChoiceField(
            label=_(<span class="hljs-string">'Category'</span>),
            group_by_field=<span class="hljs-string">'parent'</span>,
            queryset=Category.objects.all(),
        )
</code></pre>
<p>The snippet show categories with None label, I don't need to make user able to select Categories, only childs can be selected. So, I just edited the line 50 of the snippet to remove group categories.</p>
<pre><code class="lang-python">    <span class="hljs-keyword">from</span> itertools <span class="hljs-keyword">import</span> groupby

    <span class="hljs-keyword">from</span> django.forms.models <span class="hljs-keyword">import</span> ModelChoiceIterator, ModelChoiceField


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupedModelChoiceField</span>(<span class="hljs-params">ModelChoiceField</span>):</span>

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, group_by_field, group_label=None, *args, **kwargs</span>):</span>
            <span class="hljs-string">"""
            group_by_field is the name of a field on the model
            group_label is a function to return a label for each choice group
            """</span>
            super(GroupedModelChoiceField, self).__init__(*args, **kwargs)
            self.group_by_field = group_by_field
            <span class="hljs-keyword">if</span> group_label <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
                self.group_label = <span class="hljs-keyword">lambda</span> group: group
            <span class="hljs-keyword">else</span>:
                self.group_label = group_label

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_choices</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-string">"""
            Exactly as per ModelChoiceField except returns new iterator class
            """</span>
            <span class="hljs-keyword">if</span> hasattr(self, <span class="hljs-string">'_choices'</span>):
                <span class="hljs-keyword">return</span> self._choices
            <span class="hljs-keyword">return</span> GroupedModelChoiceIterator(self)
        choices = property(_get_choices, ModelChoiceField._set_choices)


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupedModelChoiceIterator</span>(<span class="hljs-params">ModelChoiceIterator</span>):</span>

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__iter__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">if</span> self.field.empty_label <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
                <span class="hljs-keyword">yield</span> (<span class="hljs-string">u""</span>, self.field.empty_label)
            <span class="hljs-keyword">if</span> self.field.cache_choices:
                <span class="hljs-keyword">if</span> self.field.choice_cache <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
                    self.field.choice_cache = [
                        (self.field.group_label(group), [
                         self.choice(ch) <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> choices])
                        <span class="hljs-keyword">for</span> group, choices <span class="hljs-keyword">in</span> groupby(
                            self.queryset.all(),
                            key=<span class="hljs-keyword">lambda</span> row: getattr(row, self.field.group_by_field))
                    ]
                <span class="hljs-keyword">for</span> choice <span class="hljs-keyword">in</span> self.field.choice_cache:
                    <span class="hljs-keyword">yield</span> choice
            <span class="hljs-keyword">else</span>:
                <span class="hljs-keyword">for</span> group, choices <span class="hljs-keyword">in</span> groupby(
                        self.queryset.all(),
                        key=<span class="hljs-keyword">lambda</span> row: getattr(
                            row, self.field.group_by_field)):
                    <span class="hljs-keyword">if</span> group <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>: <span class="hljs-comment">#Line added</span>
                        <span class="hljs-keyword">yield</span> (
                            self.field.group_label(group),
                            [self.choice(ch) <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> choices])
</code></pre>
<p>Okay, now this custom field work, but what if I need a grouped select multiple with <code>ModelMultipleChoiceField</code> ?</p>
<p>We can just add a new field like <code>GroupedModelChoiceField</code> that inherits from <code>ModelMultipleChoiceField</code></p>
<pre><code class="lang-python">    <span class="hljs-keyword">from</span> django.forms.models <span class="hljs-keyword">import</span> ModelMultipleChoiceField


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupedMultipleModelChoiceField</span>(<span class="hljs-params">ModelMultipleChoiceField</span>):</span>

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, group_by_field, group_label=None, *args, **kwargs</span>):</span>
            <span class="hljs-string">"""
            group_by_field is the name of a field on the model
            group_label is a function to return a label for each choice group
            """</span>
            super(GroupedMultipleModelChoiceField, self).__init__(*args, **kwargs)
            self.group_by_field = group_by_field
            <span class="hljs-keyword">if</span> group_label <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
                self.group_label = <span class="hljs-keyword">lambda</span> group: group
            <span class="hljs-keyword">else</span>:
                self.group_label = group_label

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_choices</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-string">"""
            Exactly as per ModelChoiceField except returns new iterator class
            """</span>
            <span class="hljs-keyword">if</span> hasattr(self, <span class="hljs-string">'_choices'</span>):
                <span class="hljs-keyword">return</span> self._choices
            <span class="hljs-keyword">return</span> GroupedModelChoiceIterator(self)
        choices = property(_get_choices, ModelMultipleChoiceField._set_choices)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Migrate Django ManyToMany field to ManyToMany through with South]]></title><description><![CDATA[When working in a project already in production, you could encounter this case
You have a model that contains a ManyToMany field without through model, Django in this case take care itself for the join table, it create a join table that contains thre...]]></description><link>https://blog.mounirmesselmeni.de/migrate-django-manytomany-field-to-manytomany-through-with-south</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/migrate-django-manytomany-field-to-manytomany-through-with-south</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Sun, 28 Jul 2013 06:52:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/tZc3vjPCk-Q/upload/v1665904669109/-23kRgpBK.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When working in a project already in production, you could encounter this case</p>
<p>You have a model that contains a <code>ManyToMany</code> field without through model, Django in this case take care itself for the join table, it create a join table that contains three columns: ID, pk of model1 and pk of model2 with a unique together constraint. At a specific time you want to add new column to the join table. Alright, with <a target="_blank" href="http://south.readthedocs.org/en/latest/">South</a> installed you can do it easly but South doesn't import your old data it's just delete old table. How to import data from old schema to the new one?</p>
<p>Let's make a concret example to be able to understand this case briefly.</p>
<ul>
<li>Create a Django project: <code>django-admin.py startproject migration_tutorial</code></li>
<li>Go into project: <code>cd migration_tutorial</code></li>
<li>Create a Django app named studies: <code>python manage.py startapp studies</code></li>
<li>In <code>migration_tutorial/migration_tutorial/settings.py</code> change database config to sqlite and add studies to <code>INSTALLED_APPS</code></li>
<li>We add also South to our <code>INSTALLED_APPS</code></li>
</ul>
<p>You should read <a target="_blank" href="http://south.readthedocs.org/en/latest/">South</a> documentation if you are not familiar with.     </p>
<p>Here's our changes in <code>settings.py</code>:</p>
<pre><code class="lang-python">
    DATABASES = {
        <span class="hljs-string">'default'</span>: {
            <span class="hljs-string">'ENGINE'</span>: <span class="hljs-string">'django.db.backends.postgresql_psycopg2'</span>,
            <span class="hljs-string">'NAME'</span>: <span class="hljs-string">'migration_tutorial'</span>,
            <span class="hljs-string">'USER'</span>: <span class="hljs-string">'postgres'</span>,
            <span class="hljs-string">'PASSWORD'</span>: <span class="hljs-string">''</span>,
            <span class="hljs-string">'HOST'</span>: <span class="hljs-string">''</span>,
            <span class="hljs-string">'PORT'</span>: <span class="hljs-string">''</span>,
        }
    }

    INSTALLED_APPS = (
        <span class="hljs-string">'django.contrib.auth'</span>,
        <span class="hljs-string">'django.contrib.contenttypes'</span>,
        <span class="hljs-string">'django.contrib.sessions'</span>,
        <span class="hljs-string">'django.contrib.sites'</span>,
        <span class="hljs-string">'django.contrib.messages'</span>,
        <span class="hljs-string">'django.contrib.staticfiles'</span>,
        <span class="hljs-string">'django.contrib.admin'</span>,

        <span class="hljs-string">'south'</span>,

        <span class="hljs-string">'studies'</span>,
    )
</code></pre>
<h2 id="heading-lets-create-our-model">Let's create our model</h2>
<p>We are going to create a simple model for Professor, Student and Course application.</p>
<ul>
<li>A Professor teach many Students</li>
<li>A Student is taught by many Professors </li>
</ul>
<p>So we have a ManyToMany relation between Students and Professors. Here's our Django model under the studies app</p>
<pre><code class="lang-python">
    <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Professor</span>(<span class="hljs-params">models.Model</span>):</span>

        first_name = models.CharField(max_length=<span class="hljs-number">50</span>)
        last_name  = models.CharField(max_length=<span class="hljs-number">50</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__unicode__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> <span class="hljs-string">u"%s %s"</span> %(self.first_name, self.last_name)


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span>(<span class="hljs-params">models.Model</span>):</span>

        first_name = models.CharField(max_length=<span class="hljs-number">50</span>)
        last_name  = models.CharField(max_length=<span class="hljs-number">50</span>)
        professors = models.ManyToManyField(Professor)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__unicode__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> <span class="hljs-string">u"%s %s"</span> %(self.first_name, self.last_name)
</code></pre>
<h2 id="heading-run-the-project">Run the project</h2>
<p>Alright, let's run our project:</p>
<ul>
<li>Init South Migration: <code>python manage.py schemamigration studies --init</code></li>
<li>Sync Database: <code>python manage.py syncdb --noinput</code></li>
<li>Migrate our app: <code>python manage.py migrate</code>, in this way South take care of schema creation</li>
</ul>
<p>Now, we are going to insert some data using shell:</p>
<p>Enter in shell mode: <code>python manage.py shell</code></p>
<pre><code class="lang-python">    &gt;&gt;&gt; <span class="hljs-keyword">from</span> studies.models <span class="hljs-keyword">import</span> Professor, Student
    &gt;&gt;&gt; professor1 = Professor(first_name=<span class="hljs-string">'Joshua'</span>, last_name=<span class="hljs-string">'Smith'</span>)
    &gt;&gt;&gt; professor1.save()
    &gt;&gt;&gt; professor2 = Professor(first_name=<span class="hljs-string">'Ethan'</span>, last_name=<span class="hljs-string">'Brown'</span>)
    &gt;&gt;&gt; professor2.save()
    &gt;&gt;&gt; student1 = Student(first_name=<span class="hljs-string">'Justin'</span>, last_name=<span class="hljs-string">'Martin'</span>)
    &gt;&gt;&gt; student1.save()
    &gt;&gt;&gt; student2 = Student(first_name=<span class="hljs-string">'Tom'</span>, last_name=<span class="hljs-string">'Taylor'</span>)
    &gt;&gt;&gt; student2.save()

    <span class="hljs-comment">#Let's add professor 1 to Student one</span>
    &gt;&gt;&gt; student1.professors.add(professor1)

    <span class="hljs-comment">#Check if data is persisted to database</span>
    &gt;&gt;&gt; student1 = Student.objects.get(first_name=<span class="hljs-string">'Justin'</span>)
    &gt;&gt;&gt; student1.professors.all()

    <span class="hljs-comment">#Let's add professor2 to student1 too</span>
    &gt;&gt;&gt; student1.professors.add(professor2)

    <span class="hljs-comment">#Professor2 to student2</span>
    &gt;&gt;&gt; student2.professors.add(professor2)
</code></pre>
<p>Note that when working with Django ManyToManyField without through, Django provide some useful method like add method we just use.</p>
<h2 id="heading-our-migration-case">Our migration case</h2>
<p>Now think if we had this model in production, we have many Students and Professors relation stored in our database. At a specific time we want to add course name taught by Professor to a student, this is a join table column. We are going no to use a through model to add this attribute.</p>
<p>Here's our updated models:</p>
<pre><code class="lang-python">    <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Professor</span>(<span class="hljs-params">models.Model</span>):</span>

        first_name = models.CharField(max_length=<span class="hljs-number">50</span>)
        last_name  = models.CharField(max_length=<span class="hljs-number">50</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__unicode__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> <span class="hljs-string">u"%s %s"</span> %(self.first_name, self.last_name)


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span>(<span class="hljs-params">models.Model</span>):</span>

        first_name = models.CharField(max_length=<span class="hljs-number">50</span>)
        last_name  = models.CharField(max_length=<span class="hljs-number">50</span>)
        professors = models.ManyToManyField(Professor, through=<span class="hljs-string">'Course'</span>)

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__unicode__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> <span class="hljs-string">u"%s %s"</span> %(self.first_name, self.last_name)


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Course</span>(<span class="hljs-params">models.Model</span>):</span>

        name      = models.CharField(max_length=<span class="hljs-number">50</span>)
        professor = models.ForeignKey(Professor)
        student   = models.ForeignKey(Student)

        <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
            unique_together = [<span class="hljs-string">'professor'</span>, <span class="hljs-string">'student'</span>]

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__unicode__</span>(<span class="hljs-params">self</span>):</span>
            <span class="hljs-keyword">return</span> <span class="hljs-string">u"%s"</span> %self.name
</code></pre>
<p>Now, we are going to use South to migrate our schemas. Run <code>python manage.py schemamigration studies --auto</code>.</p>
<p>South create a migration script under studies/migrations named <code>0002_auto__add_course__add_unique_course_professor_student.py</code></p>
<p>Here's it:</p>
<pre><code class="lang-python">    <span class="hljs-comment"># -*- coding: utf-8 -*-</span>
    <span class="hljs-keyword">import</span> datetime
    <span class="hljs-keyword">from</span> south.db <span class="hljs-keyword">import</span> db
    <span class="hljs-keyword">from</span> south.v2 <span class="hljs-keyword">import</span> SchemaMigration
    <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Migration</span>(<span class="hljs-params">SchemaMigration</span>):</span>

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forwards</span>(<span class="hljs-params">self, orm</span>):</span>
            <span class="hljs-comment"># Adding model 'Course'</span>
            db.create_table(<span class="hljs-string">u'studies_course'</span>, (
                (<span class="hljs-string">u'id'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.AutoField'</span>)(primary_key=<span class="hljs-literal">True</span>)),
                (<span class="hljs-string">'name'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.CharField'</span>)(max_length=<span class="hljs-number">50</span>)),
                (<span class="hljs-string">'professor'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>)(to=orm[<span class="hljs-string">'studies.Professor'</span>])),
                (<span class="hljs-string">'student'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>)(to=orm[<span class="hljs-string">'studies.Student'</span>])),
            ))
            db.send_create_signal(<span class="hljs-string">u'studies'</span>, [<span class="hljs-string">'Course'</span>])

            <span class="hljs-comment"># Adding unique constraint on 'Course', fields ['professor', 'student']</span>
            db.create_unique(<span class="hljs-string">u'studies_course'</span>, [<span class="hljs-string">'professor_id'</span>, <span class="hljs-string">'student_id'</span>])

            <span class="hljs-comment"># Removing M2M table for field professors on 'Student'</span>
            db.delete_table(db.shorten_name(<span class="hljs-string">u'studies_student_professors'</span>))


        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">backwards</span>(<span class="hljs-params">self, orm</span>):</span>
            <span class="hljs-comment"># Removing unique constraint on 'Course', fields ['professor', 'student']</span>
            db.delete_unique(<span class="hljs-string">u'studies_course'</span>, [<span class="hljs-string">'professor_id'</span>, <span class="hljs-string">'student_id'</span>])

            <span class="hljs-comment"># Deleting model 'Course'</span>
            db.delete_table(<span class="hljs-string">u'studies_course'</span>)

            <span class="hljs-comment"># Adding M2M table for field professors on 'Student'</span>
            m2m_table_name = db.shorten_name(<span class="hljs-string">u'studies_student_professors'</span>)
            db.create_table(m2m_table_name, (
                (<span class="hljs-string">'id'</span>, models.AutoField(verbose_name=<span class="hljs-string">'ID'</span>, primary_key=<span class="hljs-literal">True</span>, auto_created=<span class="hljs-literal">True</span>)),
                (<span class="hljs-string">'student'</span>, models.ForeignKey(orm[<span class="hljs-string">u'studies.student'</span>], null=<span class="hljs-literal">False</span>)),
                (<span class="hljs-string">'professor'</span>, models.ForeignKey(orm[<span class="hljs-string">u'studies.professor'</span>], null=<span class="hljs-literal">False</span>))
            ))
            db.create_unique(m2m_table_name, [<span class="hljs-string">'student_id'</span>, <span class="hljs-string">'professor_id'</span>])


        models = {
            <span class="hljs-string">u'studies.course'</span>: {
                <span class="hljs-string">'Meta'</span>: {<span class="hljs-string">'unique_together'</span>: <span class="hljs-string">"(['professor', 'student'],)"</span>, <span class="hljs-string">'object_name'</span>: <span class="hljs-string">'Course'</span>},
                <span class="hljs-string">u'id'</span>: (<span class="hljs-string">'django.db.models.fields.AutoField'</span>, [], {<span class="hljs-string">'primary_key'</span>: <span class="hljs-string">'True'</span>}),
                <span class="hljs-string">'name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">'professor'</span>: (<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>, [], {<span class="hljs-string">'to'</span>: <span class="hljs-string">u"orm['studies.Professor']"</span>}),
                <span class="hljs-string">'student'</span>: (<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>, [], {<span class="hljs-string">'to'</span>: <span class="hljs-string">u"orm['studies.Student']"</span>})
            },
            <span class="hljs-string">u'studies.professor'</span>: {
                <span class="hljs-string">'Meta'</span>: {<span class="hljs-string">'object_name'</span>: <span class="hljs-string">'Professor'</span>},
                <span class="hljs-string">'first_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">u'id'</span>: (<span class="hljs-string">'django.db.models.fields.AutoField'</span>, [], {<span class="hljs-string">'primary_key'</span>: <span class="hljs-string">'True'</span>}),
                <span class="hljs-string">'last_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>})
            },
            <span class="hljs-string">u'studies.student'</span>: {
                <span class="hljs-string">'Meta'</span>: {<span class="hljs-string">'object_name'</span>: <span class="hljs-string">'Student'</span>},
                <span class="hljs-string">'first_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">u'id'</span>: (<span class="hljs-string">'django.db.models.fields.AutoField'</span>, [], {<span class="hljs-string">'primary_key'</span>: <span class="hljs-string">'True'</span>}),
                <span class="hljs-string">'last_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">'professors'</span>: (<span class="hljs-string">'django.db.models.fields.related.ManyToManyField'</span>, [], {<span class="hljs-string">'to'</span>: <span class="hljs-string">u"orm['studies.Professor']"</span>, <span class="hljs-string">'through'</span>: <span class="hljs-string">u"orm['studies.Course']"</span>, <span class="hljs-string">'symmetrical'</span>: <span class="hljs-string">'False'</span>})
            }
        }

        complete_apps = [<span class="hljs-string">'studies'</span>]
</code></pre>
<p>There's two main methods:</p>
<ul>
<li>forwards: used to apply new changes</li>
<li>backwards: used to revert new changes</li>
</ul>
<p>We are going now to add custom code to import existing relation into the new schema Course.
For the attribute name in Course I'm going to insert mathematics, in some case we need some details to know what we are going to put in the added column.</p>
<p>We insert our custom code in the forwards method, it load data from old schema generated by Django and push it into the new schema Course.</p>
<pre><code class="lang-python">    <span class="hljs-comment"># -*- coding: utf-8 -*-</span>
    <span class="hljs-keyword">from</span> south.utils <span class="hljs-keyword">import</span> datetime_utils <span class="hljs-keyword">as</span> datetime
    <span class="hljs-keyword">from</span> south.db <span class="hljs-keyword">import</span> db
    <span class="hljs-keyword">from</span> south.v2 <span class="hljs-keyword">import</span> SchemaMigration
    <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

    <span class="hljs-keyword">from</span> studies.models <span class="hljs-keyword">import</span> Student, Professor, Course


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OldJoinMock</span>(<span class="hljs-params">models.Model</span>):</span>

        <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
            db_table = <span class="hljs-string">u'studies_student_professors'</span>

        student = models.ForeignKey(Student)
        professor = models.ForeignKey(Professor)


    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Migration</span>(<span class="hljs-params">SchemaMigration</span>):</span>

        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forwards</span>(<span class="hljs-params">self, orm</span>):</span>
            <span class="hljs-comment"># Adding model 'Course'</span>
            db.create_table(<span class="hljs-string">u'studies_course'</span>, (
                (<span class="hljs-string">u'id'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.AutoField'</span>)(primary_key=<span class="hljs-literal">True</span>)),
                (<span class="hljs-string">'name'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.CharField'</span>)(max_length=<span class="hljs-number">50</span>)),
                (<span class="hljs-string">'professor'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>)(to=orm[<span class="hljs-string">'studies.Professor'</span>])),
                (<span class="hljs-string">'student'</span>, self.gf(<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>)(to=orm[<span class="hljs-string">'studies.Student'</span>])),
            ))
            db.send_create_signal(<span class="hljs-string">u'studies'</span>, [<span class="hljs-string">'Course'</span>])

            <span class="hljs-comment"># Adding unique constraint on 'Course', fields ['professor', 'student']</span>
            db.create_unique(<span class="hljs-string">u'studies_course'</span>, [<span class="hljs-string">'professor_id'</span>, <span class="hljs-string">'student_id'</span>])

            <span class="hljs-comment">#Our custom code here, before delete_table and after create Course table</span>
            Course.objects.bulk_create([
                Course(
                    student=data.student,
                    professor=data.professor,
                    name=<span class="hljs-string">'mathematics'</span>)
                <span class="hljs-keyword">for</span> data <span class="hljs-keyword">in</span> OldJoinMock.objects.all()
            ])

            <span class="hljs-comment"># Removing M2M table for field professors on 'Student'</span>
            db.delete_table(db.shorten_name(<span class="hljs-string">u'studies_student_professors'</span>))


        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">backwards</span>(<span class="hljs-params">self, orm</span>):</span>
            <span class="hljs-comment"># Removing unique constraint on 'Course', fields ['professor', 'student']</span>
            db.delete_unique(<span class="hljs-string">u'studies_course'</span>, [<span class="hljs-string">'professor_id'</span>, <span class="hljs-string">'student_id'</span>])

            <span class="hljs-comment"># Deleting model 'Course'</span>
            db.delete_table(<span class="hljs-string">u'studies_course'</span>)

            <span class="hljs-comment"># Adding M2M table for field professors on 'Student'</span>
            m2m_table_name = db.shorten_name(<span class="hljs-string">u'studies_student_professors'</span>)
            db.create_table(m2m_table_name, (
                (<span class="hljs-string">'id'</span>, models.AutoField(verbose_name=<span class="hljs-string">'ID'</span>, primary_key=<span class="hljs-literal">True</span>, auto_created=<span class="hljs-literal">True</span>)),
                (<span class="hljs-string">'student'</span>, models.ForeignKey(orm[<span class="hljs-string">u'studies.student'</span>], null=<span class="hljs-literal">False</span>)),
                (<span class="hljs-string">'professor'</span>, models.ForeignKey(orm[<span class="hljs-string">u'studies.professor'</span>], null=<span class="hljs-literal">False</span>))
            ))
            db.create_unique(m2m_table_name, [<span class="hljs-string">'student_id'</span>, <span class="hljs-string">'professor_id'</span>])


        models = {
            <span class="hljs-string">u'studies.course'</span>: {
                <span class="hljs-string">'Meta'</span>: {<span class="hljs-string">'unique_together'</span>: <span class="hljs-string">"(['professor', 'student'],)"</span>, <span class="hljs-string">'object_name'</span>: <span class="hljs-string">'Course'</span>},
                <span class="hljs-string">u'id'</span>: (<span class="hljs-string">'django.db.models.fields.AutoField'</span>, [], {<span class="hljs-string">'primary_key'</span>: <span class="hljs-string">'True'</span>}),
                <span class="hljs-string">'name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">'professor'</span>: (<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>, [], {<span class="hljs-string">'to'</span>: <span class="hljs-string">u"orm['studies.Professor']"</span>}),
                <span class="hljs-string">'student'</span>: (<span class="hljs-string">'django.db.models.fields.related.ForeignKey'</span>, [], {<span class="hljs-string">'to'</span>: <span class="hljs-string">u"orm['studies.Student']"</span>})
            },
            <span class="hljs-string">u'studies.professor'</span>: {
                <span class="hljs-string">'Meta'</span>: {<span class="hljs-string">'object_name'</span>: <span class="hljs-string">'Professor'</span>},
                <span class="hljs-string">'first_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">u'id'</span>: (<span class="hljs-string">'django.db.models.fields.AutoField'</span>, [], {<span class="hljs-string">'primary_key'</span>: <span class="hljs-string">'True'</span>}),
                <span class="hljs-string">'last_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>})
            },
            <span class="hljs-string">u'studies.student'</span>: {
                <span class="hljs-string">'Meta'</span>: {<span class="hljs-string">'object_name'</span>: <span class="hljs-string">'Student'</span>},
                <span class="hljs-string">'first_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">u'id'</span>: (<span class="hljs-string">'django.db.models.fields.AutoField'</span>, [], {<span class="hljs-string">'primary_key'</span>: <span class="hljs-string">'True'</span>}),
                <span class="hljs-string">'last_name'</span>: (<span class="hljs-string">'django.db.models.fields.CharField'</span>, [], {<span class="hljs-string">'max_length'</span>: <span class="hljs-string">'50'</span>}),
                <span class="hljs-string">'professors'</span>: (<span class="hljs-string">'django.db.models.fields.related.ManyToManyField'</span>, [], {<span class="hljs-string">'to'</span>: <span class="hljs-string">u"orm['studies.Professor']"</span>, <span class="hljs-string">'through'</span>: <span class="hljs-string">u"orm['studies.Course']"</span>, <span class="hljs-string">'symmetrical'</span>: <span class="hljs-string">'False'</span>})
            }
        }

        complete_apps = [<span class="hljs-string">'studies'</span>]
</code></pre>
<p>Run migration: <code>python manage.py migrate</code></p>
<p>Let's check our data now:</p>
<pre><code class="lang-python">    &gt;&gt;&gt; <span class="hljs-keyword">from</span> studies.models <span class="hljs-keyword">import</span> Professor, Student
    &gt;&gt;&gt; student1 = Student.objects.get(first_name=<span class="hljs-string">'Justin'</span>)
    &gt;&gt;&gt; student1.professors.all() <span class="hljs-comment">#should be professor1 and professor2</span>
    ... [&lt;Professor: Joshua Smith&gt;, &lt;Professor: Ethan Brown&gt;]
    &gt;&gt;&gt; student2 = Student.objects.get(first_name=<span class="hljs-string">'Tom'</span>)
    &gt;&gt;&gt; student2.professors.all() <span class="hljs-comment">#should be professor2</span>
    ... [&lt;Professor: Ethan Brown&gt;]
</code></pre>
<p>Here's it, our relation are imported to our Course model easily. You can add also a custom code in backwards method needed for revert.</p>
<p>Just don't forget to update your code because methods add and remove are not valid now:</p>
<ul>
<li>Add: <pre><code class="lang-python">  &gt;&gt;&gt; student1.professors.add(professor1)
  <span class="hljs-comment">#Replace with</span>
  &gt;&gt;&gt; Course(student=student1, professor=professor1, name=<span class="hljs-string">'Django'</span>)
</code></pre>
</li>
<li>Remove:<pre><code class="lang-python">  &gt;&gt;&gt; student1.professors.remove(professor1)
  <span class="hljs-comment">#Replace with</span>
  &gt;&gt;&gt; Course.objects.get(student=student1, professor=professor1).delete()
</code></pre>
</li>
</ul>
<p>Last thing, use South in your Django projects, <b>you must use it</b> from project start else from now!</p>
<p>Good luck with your migrations !</p>
]]></content:encoded></item><item><title><![CDATA[Reading CSV File With Javascript and HTML5 File API]]></title><description><![CDATA[In a specific project, the client want to read a CSV file, process and show data into an OpenLayers Map.
So it come in my mind to write a simple html/javascript application, without any backend server.
How to read a file via browser ?
The unique cons...]]></description><link>https://blog.mounirmesselmeni.de/reading-csv-file-with-javascript-and-html5-file-api</link><guid isPermaLink="true">https://blog.mounirmesselmeni.de/reading-csv-file-with-javascript-and-html5-file-api</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[csv]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[js]]></category><dc:creator><![CDATA[Mounir Messelmeni]]></dc:creator><pubDate>Tue, 20 Nov 2012 07:54:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/wUbNvDTsOIc/upload/v1665904711285/bdh9dGMS2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a specific project, the client want to read a CSV file, process and show data into an <a target="_blank" href="http://openlayers.org/">OpenLayers</a> Map.</p>
<p>So it come in my mind to write a simple html/javascript application, without any backend server.</p>
<h2 id="heading-how-to-read-a-file-via-browser">How to read a file via browser ?</h2>
<p>The unique constraint was how can I read the CSV file in browser.</p>
<p>HTML5 finally provides a standard way to interact with local files, via the <a target="_blank" href="http://www.w3.org/TR/file-upload/">File API</a></p>
<p>We are going to use <a target="_blank" href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-filereader">FileReader</a></p>
<p>First, let’s check if the browser support FileReader API</p>
<pre><code class="lang-js">    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleFiles</span>(<span class="hljs-params">files</span>) </span>{
      <span class="hljs-comment">// Check for the various File API support.</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.FileReader) {
          <span class="hljs-comment">// FileReader are supported.</span>
      } <span class="hljs-keyword">else</span> {
          alert(<span class="hljs-string">'FileReader are not supported in this browser.'</span>);
      }
    }
</code></pre>
<h2 id="heading-how-to-read-a-file-via-browser">How to read a file via browser ?</h2>
<p>To select a file we’re going to use a simple html <code>&lt;input type="file"&gt;</code></p>
<p>We just call handleFile to make JavaScript returns the list of selected File objects but we are going to use only one.</p>
<pre><code class="lang-html">    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"static/js/read-csv.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"csvFileInput"</span> <span class="hljs-attr">onchange</span>=<span class="hljs-string">"handleFiles(this.files)"</span>
            <span class="hljs-attr">accept</span>=<span class="hljs-string">".csv"</span>&gt;</span>
</code></pre>
<p>##Reading the File</p>
<p>After we’ve obtained a File reference, we instantiate a FileReader object to read its contents into memory. When the load finishes, the reader’s onload event is fired and its result attribute can be used to access the file data.</p>
<p>FileReader includes four options for reading a file, asynchronously:</p>
<ul>
<li><code>FileReader.readAsBinaryString(Blob|File)</code> – The result property will contain the file/blob’s data as a binary string. Every byte is represented by an integer in the range [0..255].</li>
<li><code>FileReader.readAsText(Blob|File, opt_encoding)</code> – The result property will contain the file/blob’s data as a text string. By default the string is decoded as ‘UTF-8’. Use the optional encoding parameter can specify a different format.</li>
<li><code>FileReader.readAsDataURL(Blob|File)</code> – The result property will contain the file/blob’s data encoded as a data URL.</li>
<li><code>FileReader.readAsArrayBuffer(Blob|File)</code> – The result property will contain the file/blob’s data as an ArrayBuffer object.</li>
</ul>
<p>For our example, we want to read a CSV file, so we can read it as text. And perfom data processing after in <code>processData(csv)</code> function.</p>
<pre><code class="lang-js">    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleFiles</span>(<span class="hljs-params">files</span>) </span>{
      <span class="hljs-comment">// Check for the various File API support.</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.FileReader) {
          <span class="hljs-comment">// FileReader are supported.</span>
          getAsText(files[<span class="hljs-number">0</span>]);
      } <span class="hljs-keyword">else</span> {
          alert(<span class="hljs-string">'FileReader are not supported in this browser.'</span>);
      }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAsText</span>(<span class="hljs-params">fileToRead</span>) </span>{
      <span class="hljs-keyword">var</span> reader = <span class="hljs-keyword">new</span> FileReader();
      <span class="hljs-comment">// Read file into memory as UTF-8      </span>
      reader.readAsText(fileToRead);
      <span class="hljs-comment">// Handle errors load</span>
      reader.onload = loadHandler;
      reader.onerror = errorHandler;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadHandler</span>(<span class="hljs-params">event</span>) </span>{
      <span class="hljs-keyword">var</span> csv = event.target.result;
      processData(csv);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processData</span>(<span class="hljs-params">csv</span>) </span>{
        <span class="hljs-keyword">var</span> allTextLines = csv.split(<span class="hljs-regexp">/\r\n|\n/</span>);
        <span class="hljs-keyword">var</span> lines = [];
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i=<span class="hljs-number">0</span>; i&lt;allTextLines.length; i++) {
            <span class="hljs-keyword">var</span> data = allTextLines[i].split(<span class="hljs-string">';'</span>);
                <span class="hljs-keyword">var</span> tarr = [];
                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j=<span class="hljs-number">0</span>; j&lt;data.length; j++) {
                    tarr.push(data[j]);
                }
                lines.push(tarr);
        }
      <span class="hljs-built_in">console</span>.log(lines);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">errorHandler</span>(<span class="hljs-params">evt</span>) </span>{
      <span class="hljs-keyword">if</span>(evt.target.error.name == <span class="hljs-string">"NotReadableError"</span>) {
          alert(<span class="hljs-string">"Canno't read file !"</span>);
      }
    }
</code></pre>
<p>That’s it.</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><a target="_blank" href="https://github.com/MounirMesselmeni/html-fileapi">Source code</a> for this article.</li>
</ul>
]]></content:encoded></item></channel></rss>