<?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[Django Blog]]></title><description><![CDATA[In this blog, i share challenges and learnings throughout my software developer career.]]></description><link>https://blog.sorv.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6342125e250857e669efd365/5effd2bf-4706-4899-9173-061c016158d5.png</url><title>Django Blog</title><link>https://blog.sorv.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 15:46:36 GMT</lastBuildDate><atom:link href="https://blog.sorv.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Case Study: How I Improved Frontend Page Speed]]></title><description><![CDATA[Our landing page was visually strong, but the frontend was shipping too much too early.
The biggest symptoms were:

A Google performance score around 60 on the landing page

A very large production bu]]></description><link>https://blog.sorv.dev/case-study-how-i-improved-frontend-page-speed</link><guid isPermaLink="true">https://blog.sorv.dev/case-study-how-i-improved-frontend-page-speed</guid><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Tue, 10 Mar 2026 06:14:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6342125e250857e669efd365/b829f2e3-86b8-42d5-924a-6195e3a2879b.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our landing page was visually strong, but the frontend was shipping too much too early.</p>
<p>The biggest symptoms were:</p>
<ul>
<li><p>A Google performance score around <strong>60</strong> on the landing page</p>
</li>
<li><p>A very large production bundle</p>
</li>
<li><p>Heavy CSS and font payloads</p>
</li>
<li><p>Too much non-critical work happening during initial load</p>
</li>
</ul>
<p><em>This was the warning I getting from vite while running build command.</em></p>
<pre><code class="language-bash">
vite v6.4.1 building for production...
✓ 2527 modules transformed.
dist/index.html
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 9.64s
</code></pre>
<p>This write-up focuses only on the <strong>frontend</strong> side. API performance was intentionally left out.</p>
<hr />
<h2>The Problem</h2>
<p>From the original production build:</p>
<ul>
<li><p>Main JS bundle: <strong>1,841.83 kB</strong> minified</p>
</li>
<li><p>Main CSS bundle: <strong>269.42 kB</strong></p>
</li>
<li><p>Gzipped JS: <strong>590.54 kB</strong></p>
</li>
<li><p>Gzipped CSS: <strong>61.27 kB</strong></p>
</li>
</ul>
<p>That meant the browser had to parse, download, and evaluate far more than the landing page actually needed.</p>
<hr />
<h2>Strategy</h2>
<p>I kept the plan simple:</p>
<ol>
<li><p>Reduce the <strong>critical path</strong></p>
</li>
<li><p>Load only what the current page needs</p>
</li>
<li><p>Push non-essential work <strong>after first paint</strong></p>
</li>
<li><p>Keep regressions low by changing loading strategy first, not rewriting features</p>
</li>
</ol>
<hr />
<h2>What I Changed</h2>
<h3>1. Route-level code splitting</h3>
<p>The landing page should not pay the cost of planner screens, task editing flows, integrations, and dashboard code.</p>
<p>I changed the app so major routes load on demand instead of being pulled into the first page load.</p>
<pre><code class="language-js">const routes = [
  { path: "/", component: () =&gt; import("...HomePage...") },
  { path: "/app", component: () =&gt; import("...WorkspaceView...") },
  { path: "/settings", component: () =&gt; import("...SettingsView...") },
];
</code></pre>
<h3>2. Lazy-loading expensive UI inside authenticated screens</h3>
<p>Even inside the app, some heavy UI only appears after user interaction.</p>
<p>Examples:</p>
<ul>
<li><p>task edit modal</p>
</li>
<li><p>integration panels</p>
</li>
<li><p>filter sidebar</p>
</li>
<li><p>device-specific overlays</p>
</li>
</ul>
<p>So instead of bundling them into the initial workspace render, I loaded them only when needed.</p>
<pre><code class="language-js">const HeavyModal = defineAsyncComponent(() =&gt; import("...TaskEditor..."));
</code></pre>
<p>you can read more about defineAsyncComponent on <a href="https://vuejs.org/guide/components/async">official docs</a>.</p>
<h3>3. Deferring analytics and monitoring</h3>
<p>Analytics, session tracking, and error monitoring are useful, but they do not need to compete with first paint.</p>
<p>I moved them behind a delayed, idle-time startup.</p>
<pre><code class="language-js">runAfterLoadAndIdle(() =&gt; {
  setTimeout(() =&gt; {
    initAnalytics();
    initMonitoring();
    loadThirdPartyScript();
  }, 4000);
});
</code></pre>
<p>This was one of the highest-leverage changes for Lighthouse.</p>
<h3>4. Font loading discipline</h3>
<p>The previous build was shipping a wide set of font families and language subsets up front.</p>
<p>I changed that strategy to:</p>
<ul>
<li><p>load only base fonts first</p>
</li>
<li><p>load landing-page accent fonts only on the landing page</p>
</li>
<li><p>load theme-specific fonts only when that theme is active</p>
</li>
<li><p>use narrower subsets instead of pulling everything by default</p>
</li>
</ul>
<pre><code class="language-js">loadBaseFonts();
loadLandingFonts();
loadThemeFonts(currentTheme);
</code></pre>
<h3>5. Reducing work on the landing page itself</h3>
<p>The landing page also had runtime work that looked nice but did not need to happen immediately.</p>
<p>I optimized that by:</p>
<ul>
<li><p>delaying visual effects until idle time</p>
</li>
<li><p>keeping below-the-fold sections out of the initial rendering cost</p>
</li>
<li><p>adding image dimensions to reduce layout work</p>
</li>
<li><p>preserving design while trimming early execution</p>
</li>
</ul>
<hr />
<h2>Benchmarks</h2>
<h2>Build Footprint</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>Main landing-path JS</td>
<td>1,841.83 kB</td>
<td>about <strong>237 kB</strong> critical JS</td>
</tr>
<tr>
<td>Main landing-path CSS</td>
<td>269.42 kB</td>
<td>about <strong>24 kB</strong> critical CSS</td>
</tr>
</tbody></table>
<h2>Lighthouse Snapshot</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>Landing page performance score</td>
<td>~60</td>
<td>93</td>
</tr>
</tbody></table>
<h3>Notes:</h3>
<ul>
<li><p>The new JS number reflects the critical landing path, not the entire app footprint.</p>
</li>
<li><p>Large authenticated features still exist, but they are no longer loaded up front on the landing page.</p>
</li>
</ul>
<hr />
<h2>Why This Worked</h2>
<p>The biggest lesson was straightforward:</p>
<blockquote>
<p>I did not need less product. I needed less product in the first request.</p>
</blockquote>
<p>Most performance issues were not caused by one huge mistake. They came from many reasonable frontend decisions stacking together:</p>
<ul>
<li><p>eager route imports</p>
</li>
<li><p>global loading of rare UI</p>
</li>
<li><p>early analytics boot</p>
</li>
<li><p>broad font loading</p>
</li>
<li><p>decorative runtime work too early</p>
</li>
</ul>
<p>Once I treated the landing page like a focused entry experience instead of the whole app, performance improved quickly.</p>
<hr />
<h2>Takeaways</h2>
<ul>
<li><p>Start with the <strong>critical path</strong>, not micro-optimizations</p>
</li>
<li><p>Route splitting gives outsized wins when a landing page and app shell live together</p>
</li>
<li><p>Third-party scripts should earn their place in the first few seconds</p>
</li>
<li><p>Font strategy matters more than many teams think</p>
</li>
<li><p>Good performance work is often about <strong>loading order</strong>, not feature removal</p>
</li>
</ul>
<hr />
<h2>Final Summary</h2>
<p>I improved page speed by shrinking the initial frontend payload, deferring non-essential work, and keeping heavy product features out of the landing-page critical path.</p>
<p>The result was a much lighter first load, better rendering behavior, and a local Lighthouse score that moved from the low-performance range into the <strong>90+</strong> range without redesigning the product.</p>
<h2><em><strong>Before you go:</strong></em></h2>
<p>if you're someone struggling with managing time, then checkout this light weight but powerful web app <a href="https://lazyplanner.app">Lazy Planner</a><br />i made this for myself first because i couldn't find the one that better suits my need but i believe if it's useful for me, it could be useful for more people too.</p>
]]></content:encoded></item><item><title><![CDATA[Fixing Circular Import Errors]]></title><description><![CDATA[alright, this is a quick write about the circular import error i faced recently. the project had so many files and imports and i knew it would end up like this someday but now when i saw import errors, i was procrastinating a lot since i was feeling ...]]></description><link>https://blog.sorv.dev/fixing-circular-import-errors</link><guid isPermaLink="true">https://blog.sorv.dev/fixing-circular-import-errors</guid><category><![CDATA[Python]]></category><category><![CDATA[circular import in python]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Wed, 11 Feb 2026 03:14:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770778912745/479555d7-2280-4012-99e2-9c3d3bb97f4a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>alright, this is a quick write about the circular import error i faced recently. the project had so many files and imports and i knew it would end up like this someday but now when i saw import errors, i was procrastinating a lot since i was feeling too lazy to traverse so many to trace the issues. also i need to restructured the files again which again another challenge when u got so many files.</p>
<p>anyway i was exploring and i found this package <a target="_blank" href="https://pylint.readthedocs.io/en/latest/additional_tools/pyreverse/index.html#pyreverse">pyreverse</a>. it’s so easy to generate a mind map of imports.</p>
<p>use this cmd in root folder but make sure your project’s root folder have <code>__init__.py</code> file.<br />then run</p>
<p><code>pyreverse -o png .</code></p>
<p>this will need graphviz library to generate png images which u can install using apt or brew install graphviz but u can also use other format to export. check pyreverse docs for more details about different formats.</p>
<p>understanding this graph is simple too. the arrows should go upward and there shouldn’t be arrows pointing at each other. if they are pointing at each other, you probably already have circular import issues and errors will be shown when you run the code. but if error isn’t raised, still it’s a good idea to rethink about project imports as you’re close to circular import errors.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Django's Password Hashing Mechanism: A Developer’s Guide]]></title><description><![CDATA[Password hashing is a critical piece of any authentication system, and Django’s approach ensures both security and flexibility. This article will walk you through the key concepts behind Django’s password hashing, how it uses salts, and how login ver...]]></description><link>https://blog.sorv.dev/understanding-djangos-password-hashing-mechanism-a-developers-guide</link><guid isPermaLink="true">https://blog.sorv.dev/understanding-djangos-password-hashing-mechanism-a-developers-guide</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[django orm]]></category><category><![CDATA[Python]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sun, 21 Sep 2025 08:00:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/DoWZMPZ-M9s/upload/5bbf2ba0f92e7913edea21cc668d31af.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Password hashing is a critical piece of any authentication system, and Django’s approach ensures both security and flexibility. This article will walk you through the key concepts behind Django’s password hashing, how it uses salts, and how login verification works.</p>
<h2 id="heading-why-password-hashing-matters">Why Password Hashing Matters</h2>
<p>Storing passwords in plain text is an absolute no-go. If your database gets compromised, plain passwords put your users at immense risk. Instead, Django uses <strong>password hashing</strong>, which irreversibly transforms a password into a fixed-length string. This way, even if someone accesses your database, they won’t see the actual passwords.</p>
<h2 id="heading-what-algorithm-does-django-use">What Algorithm Does Django Use?</h2>
<p>By default, Django uses the <strong>PBKDF2 algorithm with SHA-256</strong> as its hashing method. PBKDF2 (Password-Based Key Derivation Function 2) applies the SHA-256 hash function many thousands of times — typically <strong>260,000 iterations or more</strong> — making brute-force attacks computationally expensive and slow.</p>
<p>Django also supports alternative hashing algorithms like Argon2, BCryptSHA256, and Scrypt, which you can configure in your settings if desired.</p>
<h2 id="heading-the-role-of-salt-in-password-hashing">The Role of Salt in Password Hashing</h2>
<p>A core ingredient in Django’s password hashing is the <strong>salt</strong>: a unique, randomly generated string added to each password before hashing. The purpose of this salt is to guarantee that even if two users have the same password, their hashed values will differ. This protects against rainbow table attacks, which rely on precomputed hashes to reverse-engineer passwords.</p>
<p>Importantly, Django does <strong>not</strong> keep the salt secret. Instead, it stores the salt alongside the hashed password in the database. This allows Django to use the <strong>same salt again during login verification</strong>, ensuring accurate comparison.</p>
<h2 id="heading-what-does-a-hashed-password-look-like">What Does a Hashed Password Look Like?</h2>
<p>When you inspect Django’s user database, passwords are stored as strings with a specific format:</p>
<pre><code class="lang-bash">algorithm<span class="hljs-variable">$iterations</span><span class="hljs-variable">$salt</span><span class="hljs-variable">$hashed_password</span>
</code></pre>
<p>For example:</p>
<pre><code class="lang-bash">textpbkdf2_sha256<span class="hljs-variable">$260000</span><span class="hljs-variable">$ks9a7bsZqP11</span><span class="hljs-variable">$hbN5XwLoKA54tJdIQcVepXI</span>/vXftN4Br2nqSiwDrTyM=
</code></pre>
<p>Breaking it down:</p>
<ul>
<li><p><code>pbkdf2_sha256</code>: The hashing algorithm used</p>
</li>
<li><p><code>260000</code>: Number of hash iterations</p>
</li>
<li><p><code>ks9a7bsZqP11</code>: The random salt</p>
</li>
<li><p>The last part is the actual hashed password</p>
</li>
</ul>
<h2 id="heading-how-password-verification-works-during-login">How Password Verification Works During Login</h2>
<p>When a user attempts to log in, Django does the following:</p>
<ol>
<li><p>It fetches the stored hashed password string from the database.</p>
</li>
<li><p>Extracts the components: algorithm, iteration count, and salt.</p>
</li>
<li><p>Re-hashes the password entered by the user with <strong>those exact parameters</strong>.</p>
</li>
<li><p>Compares the freshly hashed value with the stored hash.</p>
</li>
<li><p>If they match, the password is correct. If not, access is denied.</p>
</li>
</ol>
<p>Because the salt is stored with the hash, Django can reproduce the hashing process precisely.</p>
<hr />
<h2 id="heading-what-about-the-secretkey">What About the SECRET_KEY?</h2>
<p>It’s a common misconception that Django’s <code>SECRET_KEY</code> is involved in password hashing. It isn’t. The secret key is primarily used for cryptographic signing elsewhere in Django (e.g., sessions, CSRF tokens) and is <strong>not</strong> part of password hashing.</p>
<h2 id="heading-customizing-password-hashing">Customizing Password Hashing</h2>
<p>Django’s flexibility shines here. You can customize the <code>PASSWORD_HASHERS</code> setting to choose your preferred algorithms. For example, you can switch to Argon2 for stronger security, or add multiple hashers to support legacy passwords during a transition. New passwords will be hashed with the first hasher in your list.</p>
<hr />
<h2 id="heading-summary">Summary</h2>
<ul>
<li><p>Django uses PBKDF2 with SHA-256 and a high iteration count by default.</p>
</li>
<li><p>Each password is uniquely salted with a random string stored alongside the hash.</p>
</li>
<li><p>Passwords are stored in a format combining algorithm, iteration count, salt, and hashed password.</p>
</li>
<li><p>Login verification reuses the stored salt and parameters for hashing the entered password.</p>
</li>
<li><p>Django’s secret key is unrelated to password hashing.</p>
</li>
<li><p>You can customize which hashing algorithms Django uses via settings.</p>
</li>
</ul>
<p>Understanding these mechanics ensures you appreciate Django’s secure design and can confidently manage user authentication in your projects.</p>
]]></content:encoded></item><item><title><![CDATA[Deployment Guide for Django + Vue.js project on VPS]]></title><description><![CDATA[Overview
This guide provides a secure, production-ready deployment process for the Focus Timer application on a Linux VPS. It follows best practices at each step and includes brief explanations to help beginners understand.
Prerequisites

A fresh Ubu...]]></description><link>https://blog.sorv.dev/deployment-guide-for-django-vuejs-project-on-vps</link><guid isPermaLink="true">https://blog.sorv.dev/deployment-guide-for-django-vuejs-project-on-vps</guid><category><![CDATA[deployment]]></category><category><![CDATA[hosting]]></category><category><![CDATA[Django]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[vps]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Wed, 04 Jun 2025 08:57:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/rUTpMpG6GEQ/upload/2f5bbf9f56d475a1e2c6a569a03c09f0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>This guide provides a secure, production-ready deployment process for the Focus Timer application on a Linux VPS. It follows best practices at each step and includes brief explanations to help beginners understand.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>A fresh Ubuntu/Debian VPS (18.04+ or 20.04+).</p>
</li>
<li><p>Root or sudo access.</p>
</li>
<li><p>A domain name pointing to your VPS.</p>
</li>
<li><p>Basic familiarity with Linux shell.</p>
</li>
</ul>
<h2 id="heading-1-vps-setup">1. VPS Setup</h2>
<h3 id="heading-11-create-unprivileged-user">1.1 Create Unprivileged User</h3>
<p><em>Why?</em> Running services as non-root improves security.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Add a user, no password login, add to sudo group</span>
adduser --disabled-password --gecos <span class="hljs-string">""</span> focususer
usermod -aG sudo focususer
<span class="hljs-comment"># Set up SSH access for the new user</span>
<span class="hljs-comment"># Copy root's authorized keys to the new user</span>
mkdir -p /home/focususer/.ssh
cp /root/.ssh/authorized_keys /home/focususer/.ssh/authorized_keys
<span class="hljs-comment"># Set ownership and permissions</span>
chown -R focususer:focususer /home/focususer/.ssh
chmod 700 /home/focususer/.ssh
chmod 600 /home/focususer/.ssh/authorized_keys
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>adduser</code>: Creates a new Linux user with default settings.</p>
</li>
<li><p><code>--disabled-password</code>: Prevents setting a login password (forces SSH key auth only).</p>
</li>
<li><p><code>--gecos ""</code>: Supplies empty fields for the user's full name and contact info.</p>
</li>
<li><p><code>usermod -aG sudo</code>: Appends (<code>-a</code>) the user to the <code>sudo</code> group, allowing administrative commands.</p>
</li>
</ul>
<h3 id="heading-12-secure-ssh-access">1.2 Secure SSH Access</h3>
<p><em>Why?</em> Prevent unauthorized root or password-based logins.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># On your local machine, generate SSH key if needed:</span>
ssh-keygen -t ed25519 -C <span class="hljs-string">"your_email@example.com"</span>

<span class="hljs-comment"># Copy public key to server:</span>
ssh-copy-id focususer@your.domain.com
</code></pre>
<h3 id="heading-on-server-edit-etcsshsshdconfig">On server, edit /etc/ssh/sshd_config:</h3>
<pre><code class="lang-bash">  Port 22
  PermitRootLogin no
  PasswordAuthentication no
  ChallengeResponseAuthentication no
  UsePAM yes
  sudo systemctl reload ssh
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>ssh-keygen -t ed25519</code>: Generates a modern, high-security Ed25519 key pair.</p>
</li>
<li><p><code>-C "comment"</code>: Adds an identifying comment (often your email).</p>
</li>
<li><p><code>ssh-copy-id</code>: Installs your public key on the remote server's <code>~/.ssh/authorized_keys</code> file, allowing key-based logins.</p>
</li>
<li><p><code>PermitRootLogin no</code>: Disables SSH login as <code>root</code>, forcing administrative access via a less-privileged user plus <code>sudo</code>.</p>
</li>
<li><p><code>PasswordAuthentication no</code>: Turns off password-based logins entirely; only key-based auth is allowed.</p>
</li>
<li><p><code>ChallengeResponseAuthentication no</code>: Disables keyboard-interactive (challenge-response) methods, such as one-time passwords or other PAM-driven prompts, preventing any fallback from key-based authentication.</p>
<ul>
<li>Without this setting, SSH could invoke PAM's challenge modules (e.g. OTP or custom scripts) that might allow weaker or interactive authentication paths.</li>
</ul>
</li>
<li><p><code>UsePAM yes</code>: Enables Pluggable Authentication Modules (PAM) integration.</p>
<ul>
<li><p>Even with password logins disabled, PAM handles account and session management after a successful key login.</p>
</li>
<li><p>PAM modules enforce additional security policies (e.g. account expiration, lockouts, resource limits, logging).</p>
</li>
<li><p>Ensures that system-wide policies (fail2ban, pam_tally2, custom modules) can apply to every SSH session.</p>
</li>
</ul>
</li>
<li><p><code>systemctl reload ssh</code>: Applies the above SSH daemon changes without dropping existing connections.</p>
</li>
</ul>
<h4 id="heading-121-logging-in-as-focususer">1.2.1. Logging in as focususer</h4>
<p><em>Why?</em> Operating as a non-root user mitigates the risk of accidental system-wide changes.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Exit the current root SSH session:</span>
<span class="hljs-built_in">exit</span>

<span class="hljs-comment"># From your local machine, SSH back in as the unprivileged user:</span>
ssh focususer@your.domain.com
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>exit</code>: Ends the current SSH session as root and returns you to your local shell.</p>
</li>
<li><p><code>ssh focususer@your.domain.com</code>: Initiates a new SSH session using your key for the <code>focususer</code> account.</p>
</li>
</ul>
<h3 id="heading-13-firewall-amp-fail2ban">1.3 Firewall &amp; Fail2ban</h3>
<p><em>Why?</em> Limit open ports and block brute-force attempts.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install UFW &amp; Fail2ban</span>
sudo apt update &amp;&amp; sudo apt install -y ufw fail2ban

<span class="hljs-comment"># Basic UFW rules</span>
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow <span class="hljs-string">'Nginx Full'</span>
sudo ufw <span class="hljs-built_in">enable</span>

<span class="hljs-comment"># Configure Fail2ban (e.g., /etc/fail2ban/jail.local)</span>
sudo tee /etc/fail2ban/jail.local &lt;&lt;EOF
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 5

[sshd]
enabled = <span class="hljs-literal">true</span>
port    = 22
filter  = sshd
logpath = /var/<span class="hljs-built_in">log</span>/auth.log
EOF
sudo systemctl restart fail2ban
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>ufw default deny incoming</code>: Blocks all incoming traffic by default.</p>
</li>
<li><p><code>ufw default allow outgoing</code>: Allows all outbound traffic.</p>
</li>
<li><p><code>ufw allow OpenSSH</code>: Opens SSH port (usually 22).</p>
</li>
<li><p><code>ufw allow 'Nginx Full'</code>: Opens HTTP (80) and HTTPS (443) for web traffic.</p>
</li>
<li><p><code>ufw enable</code>: Activates the firewall with the defined rules.</p>
</li>
<li><p><code>fail2ban</code>: Monitors log files and bans IPs after too many failed login attempts.</p>
</li>
<li><p>In <code>jail.local</code>:</p>
<ul>
<li><p><code>bantime</code>: Duration (1h) to ban offenders.</p>
</li>
<li><p><code>findtime</code>: Time window (10m) to track failures.</p>
</li>
<li><p><code>maxretry</code>: Max allowed failures (5) before ban.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-14-automatic-security-updates">1.4 Automatic Security Updates</h3>
<p><em>Why?</em> Keep critical packages patched.</p>
<pre><code class="lang-bash">sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>unattended-upgrades</code>: Automatically downloads and installs security updates.</p>
</li>
<li><p><code>dpkg-reconfigure unattended-upgrades</code>: Opens a configuration prompt to enable auto-updates.</p>
</li>
</ul>
<h2 id="heading-2-system-dependencies">2. System Dependencies</h2>
<p><em>Install tools required for building and deployment.</em></p>
<pre><code class="lang-bash">sudo apt install -y git build-essential curl libpq-dev
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>git</code>: Version control system to clone your repository.</p>
</li>
<li><p><code>build-essential</code>: Installs GCC, make, and other build tools for compiling native extensions.</p>
</li>
<li><p><code>curl</code>: Tool to transfer data from or to a server (used for downloading scripts).</p>
</li>
<li><p><code>libpq-dev</code>: Development headers for PostgreSQL, required by psycopg2 Python package.</p>
</li>
</ul>
<h2 id="heading-3-database-postgresql">3. Database: PostgreSQL</h2>
<pre><code class="lang-bash">sudo apt install -y postgresql postgresql-contrib

<span class="hljs-comment"># Create database and user</span>
check ./setup.md <span class="hljs-keyword">for</span> PostgreSQL setup

<span class="hljs-comment"># Secure PostgreSQL: only listen locally and enforce password auth</span>
sudo sed -i <span class="hljs-string">"s/#listen_addresses = 'localhost'/listen_addresses = 'localhost'/"</span> /etc/postgresql/*/main/postgresql.conf
<span class="hljs-comment"># Only allow local socket and password auth for TCP</span>
host    all             all             127.0.0.1/32            md5
sudo systemctl restart postgresql
</code></pre>
<p><em>Brief:</em> PostgreSQL is reliable and scalable for production.</p>
<h2 id="heading-4-redis-for-celery">4. Redis for Celery</h2>
<pre><code class="lang-bash">sudo apt install -y redis-server
sudo systemctl <span class="hljs-built_in">enable</span> --now redis-server.service

<span class="hljs-comment"># Secure Redis: bind only to localhost, enable protected mode, optional password</span>
sudo sed -i <span class="hljs-string">"s/^bind .*/bind 127.0.0.1 ::1/"</span> /etc/redis/redis.conf
sudo sed -i <span class="hljs-string">"s/^protected-mode no/protected-mode yes/"</span> /etc/redis/redis.conf
sudo sed -i <span class="hljs-string">"s/# requirepass foobared/requirepass StrongRedisPassw0rd/"</span> /etc/redis/redis.conf
sudo systemctl restart redis
</code></pre>
<p><em>Brief:</em> Redis acts as broker and result backend for Celery.</p>
<h2 id="heading-5-codebase-deployment">5. Codebase Deployment</h2>
<h3 id="heading-51-clone-repository">5.1 Clone Repository</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Switch to DIR where you want to keep your project</span>
<span class="hljs-comment"># ( using a sample project name, replace with your actual repo )</span>
git <span class="hljs-built_in">clone</span> https://github.com/your-repo/focus-timer-django-vue.git
<span class="hljs-built_in">cd</span> focus-timer-django-vue
</code></pre>
<h3 id="heading-52-environment-variables">5.2 Environment Variables</h3>
<p><em>Why?</em> Keep secrets out of source code.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># In project root, create .env</span>
touch .env
<span class="hljs-comment"># Populate .env with necessary variables</span>
<span class="hljs-comment"># Secure .env file permissions</span>
chmod 600 .env
</code></pre>
<h2 id="heading-6-python-virtual-environment">6. Python Virtual Environment</h2>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> backend
python3 -m venv .venv
<span class="hljs-built_in">source</span> .venv/bin/activate
pip install uv
pip install -r requirements.txt
</code></pre>
<h2 id="heading-7-frontend-build-vuejs">7. Frontend Build (Vue.js)</h2>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ../frontend-vue
<span class="hljs-comment"># install node</span>
curl -fsSL https://raw.githubusercontent.com/mklement0/n-install/stable/bin/n-install | bash -s 22

npm run build
</code></pre>
<p><em>Brief:</em> Outputs static assets in <code>dist/</code>.</p>
<h2 id="heading-8-django-migrations-amp-static-files">8. Django Migrations &amp; Static Files</h2>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ../backend
<span class="hljs-built_in">source</span> .venv/bin/activate
python manage.py migrate
python manage.py collectstatic --noinput
</code></pre>
<h2 id="heading-9-application-server-gunicorn">9. Application Server: Gunicorn</h2>
<pre><code class="lang-bash">pip install gunicorn
</code></pre>
<p>Create <code>/etc/systemd/system/gunicorn.service</code>:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=Gunicorn daemon for Focus Timer
<span class="hljs-attr">After</span>=network.target

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">User</span>=focususer
<span class="hljs-attr">Group</span>=www-data
<span class="hljs-attr">WorkingDirectory</span>=/home/focususer/focus-timer-django-vue/backend
<span class="hljs-attr">ExecStart</span>=/home/focususer/focus-timer-django-vue/.venv/bin/gunicorn \
  backend.wsgi:application \
  --bind 127.0.0.1:8000 \
  --workers 4
<span class="hljs-attr">EnvironmentFile</span>=/home/focususer/focus-timer-django-vue/.env
<span class="hljs-attr">Restart</span>=<span class="hljs-literal">on</span>-failure
<span class="hljs-attr">PrivateTmp</span>=<span class="hljs-literal">true</span>

<span class="hljs-section">[Install]</span>
<span class="hljs-attr">WantedBy</span>=multi-user.target
</code></pre>
<p>Enable and start:</p>
<pre><code class="lang-bash">sudo systemctl daemon-reload
sudo systemctl <span class="hljs-built_in">enable</span> --now gunicorn
</code></pre>
<h2 id="heading-10-background-workers-celery-amp-beat">10. Background Workers: Celery &amp; Beat</h2>
<ul>
<li>Create <code>/etc/systemd/system/celery.service</code>:</li>
</ul>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=Celery worker service for Focus Timer
<span class="hljs-attr">After</span>=network.target

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">Type</span>=simple
<span class="hljs-attr">User</span>=focususer
<span class="hljs-attr">Group</span>=www-data
<span class="hljs-attr">WorkingDirectory</span>=/home/focususer/focus-timer-django-vue/backend
<span class="hljs-attr">EnvironmentFile</span>=/home/focususer/focus-timer-django-vue/.env
<span class="hljs-attr">ExecStart</span>=/home/focususer/focus-timer-django-vue/.venv/bin/celery -A backend worker \
  <span class="hljs-attr">--loglevel</span>=info
<span class="hljs-attr">Restart</span>=<span class="hljs-literal">on</span>-failure

<span class="hljs-section">[Install]</span>
<span class="hljs-attr">WantedBy</span>=multi-user.target
</code></pre>
<ul>
<li>Create <code>/etc/systemd/system/celery-beat.service</code>:</li>
</ul>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=Celery Beat scheduler for Focus Timer
<span class="hljs-attr">After</span>=network.target

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">Type</span>=simple
<span class="hljs-attr">User</span>=focususer
<span class="hljs-attr">Group</span>=www-data
<span class="hljs-attr">WorkingDirectory</span>=/home/focususer/focus-timer-django-vue/backend
<span class="hljs-attr">EnvironmentFile</span>=/home/focususer/focus-timer-django-vue/.env
<span class="hljs-attr">ExecStart</span>=/home/focususer/focus-timer-django-vue/.venv/bin/celery -A backend beat \
  <span class="hljs-attr">--loglevel</span>=info  \
  --scheduler django_celery_beat.schedulers:DatabaseScheduler
<span class="hljs-attr">Restart</span>=<span class="hljs-literal">on</span>-failure

<span class="hljs-section">[Install]</span>
<span class="hljs-attr">WantedBy</span>=multi-user.target
</code></pre>
<ul>
<li>Enable &amp; start both services:</li>
</ul>
<pre><code class="lang-bash">sudo systemctl daemon-reload
sudo systemctl <span class="hljs-built_in">enable</span> --now celery celery-beat
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>Type=forking/simple</code>: ensures proper startup and process tracking.</p>
</li>
<li><p><code>--detach</code>: runs worker/beat in background.</p>
</li>
<li><p><code>Restart=on-failure</code>: automatically recovers from crashes.</p>
</li>
</ul>
<h2 id="heading-11-reverse-proxy-nginx">11. Reverse Proxy: Nginx</h2>
<p>Install and configure <code>/etc/nginx/sites-available/focus-timer</code>:</p>
<pre><code class="lang-nginx"><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
    <span class="hljs-attribute">server_name</span> tymr.online www.tymr.online;

    <span class="hljs-attribute">root</span> /home/focususer/focus-timer-django-vue/frontend-vue/dist;
    <span class="hljs-attribute">index</span> index.html;

    <span class="hljs-comment"># incase using websockets</span>
    <span class="hljs-attribute">location</span> /ws/ {
        <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:8000;
        <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;

        <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>;
        <span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">"upgrade"</span>;
        <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$remote_addr</span>;

        <span class="hljs-attribute">proxy_read_timeout</span> <span class="hljs-number">86400</span>;
    }

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ /index.html;
    }
    <span class="hljs-attribute">location</span> /static/ {
        <span class="hljs-attribute">alias</span> /home/focususer/focus-timer-django-vue/backend/static/;
    }
    <span class="hljs-attribute">location</span> /api/    { <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:8000/api/; <span class="hljs-attribute">include</span> proxy_params; }
    <span class="hljs-attribute">location</span> /auth/   { <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:8000/auth/; <span class="hljs-attribute">include</span> proxy_params; }
    <span class="hljs-attribute">location</span> /admin/  { <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:8000/admin/; <span class="hljs-attribute">include</span> proxy_params; }

    <span class="hljs-comment"># Security headers</span>
    <span class="hljs-attribute">add_header</span> X-Frame-Options <span class="hljs-string">"SAMEORIGIN"</span> always;
    <span class="hljs-attribute">add_header</span> X-Content-Type-Options <span class="hljs-string">"nosniff"</span> always;
    <span class="hljs-attribute">add_header</span> Referrer-Policy <span class="hljs-string">"no-referrer-when-downgrade"</span> always;
    <span class="hljs-attribute">add_header</span> Strict-Transport-Security <span class="hljs-string">"max-age=31536000; includeSubDomains; preload"</span> always;
    <span class="hljs-attribute">client_max_body_size</span> <span class="hljs-number">10M</span>;
}
</code></pre>
<p>Enable and reload:</p>
<pre><code class="lang-bash">sudo ln -s /etc/nginx/sites-available/focus-timer /etc/nginx/sites-enabled/
sudo nginx -t
sudo ln -s /etc/nginx/sites-available/focus-timer-redirect /etc/nginx/sites-enabled/
sudo systemctl reload nginx
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p>Security headers prevent clickjacking, MIME-sniffing, enforce HTTPS.</p>
</li>
<li><p><code>client_max_body_size</code>: limits upload size to mitigate DoS.</p>
</li>
<li><p>HTTP-&gt;HTTPS redirect ensures all traffic is encrypted.</p>
</li>
</ul>
<h2 id="heading-12-ssltls-with-lets-encrypt">12. SSL/TLS with Let's Encrypt</h2>
<pre><code class="lang-bash">sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d tymr.online -d www.tymr.online
</code></pre>
<p><em>Brief:</em> Provides free, auto-renewing certificates.</p>
<h2 id="heading-13-monitoring-amp-logging">13. Monitoring &amp; Logging</h2>
<ul>
<li><p>Use <code>journalctl -u gunicorn -f</code> and <code>journalctl -u celery -f</code>.</p>
</li>
<li><p>Set up Logrotate for Gunicorn logs if needed.</p>
</li>
<li><p>Consider external monitoring (Prometheus/Grafana, UptimeRobot).</p>
</li>
</ul>
<h2 id="heading-14-automated-backups">14. Automated Backups</h2>
<ul>
<li><p>Write a cron job for nightly PostgreSQL dumps:</p>
<pre><code class="lang-bash">  sudo crontab -u focususer -e
  <span class="hljs-comment"># Add:</span>
  0 2 * * * pg_dump -U focusdbuser focusdb | gzip &gt; ~/backups/db-$(date +\%F).sql.gz
</code></pre>
</li>
<li><p>Secure backup storage (offsite or S3).</p>
</li>
</ul>
<h2 id="heading-15-routine-maintenance">15. Routine Maintenance</h2>
<ul>
<li><p>Keep OS &amp; packages updated:</p>
<pre><code class="lang-bash">  sudo apt update &amp;&amp; sudo apt upgrade -y
</code></pre>
</li>
<li><p>Review logs, monitor disk and memory.</p>
</li>
</ul>
<h2 id="heading-16-nginx-permissions">16. Nginx permissions</h2>
<p><em>Why?</em> Nginx needs access to static files and directories.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Ensure Nginx can read static files</span>
sudo chmod o+x /home/focususer
sudo chmod -R o+rX /home/focususer/focus-timer-django-vue/frontend-vue/dist
sudo chmod -R o+rX /home/focususer/focus-timer-django-vue/backend/static
sudo chmod -R o+rX /home/focususer/focus-timer-django-vue/backend/media
sudo chmod -R o+x /home/focususer/focus-timer-django-vue/frontend-vue
chmod +x /home/focususer/focus-timer-django-vue/.venv/bin/gunicorn
chmod +x /home/focususer/focus-timer-django-vue/.venv/bin/celery
chmod u+x restart_all.sh
</code></pre>
<h1 id="heading-few-helpful-commands-to-restart-services-after-deployment">Few helpful commands to restart services after deployment</h1>
<h1 id="heading-1-reload-all-systemd-unit-files-after-any-edits">1. Reload all systemd unit files (after any edits)</h1>
<p>sudo systemctl daemon-reload</p>
<h1 id="heading-2-restart-your-django-app-gunicorn">2. Restart your Django app (Gunicorn)</h1>
<p>sudo systemctl restart gunicorn sudo systemctl status gunicorn # check exit status immediately journalctl -u gunicorn -f # live logs</p>
<h1 id="heading-3-restart-celery-worker-amp-beat">3. Restart Celery worker &amp; beat</h1>
<p>sudo systemctl restart celery celery-beat sudo systemctl status celery # check worker status sudo systemctl status celery-beat journalctl -u celery -f # live worker logs journalctl -u celery-beat -f # live beat logs</p>
<h1 id="heading-4-restart-nginx-reverse-proxy-amp-static-files">4. Restart Nginx (reverse proxy &amp; static files)</h1>
<p>sudo systemctl restart nginx sudo systemctl status nginx sudo journalctl -u nginx -f</p>
<h1 id="heading-5-optional-restart-backing-services">5. (Optional) Restart backing services</h1>
<p>sudo systemctl restart redis-server postgresql sudo systemctl status redis-server postgresql sudo journalctl -u redis-server -f sudo journalctl -u postgresql -f</p>
<h1 id="heading-6-optional-i-made-a-script-to-restart-all-services-at-once-feel-free-to-tweak-it-as-it-fits-your-needs">6. (Optional) I made a script to restart all services at once. Feel free to tweak it as it fits your needs.</h1>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-comment"># Script to restart all Focus Timer services</span>

<span class="hljs-built_in">set</span> -euo pipefail

<span class="hljs-comment"># Parse options</span>
SKIP_FRONTEND=<span class="hljs-literal">false</span>
<span class="hljs-keyword">for</span> arg <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span>; <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">case</span> <span class="hljs-variable">$arg</span> <span class="hljs-keyword">in</span>
    --skip-frontend)
      SKIP_FRONTEND=<span class="hljs-literal">true</span>
      ;;
  <span class="hljs-keyword">esac</span>
<span class="hljs-keyword">done</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Installing backend requirements..."</span>
.venv/bin/uv pip install -r backend/requirements.txt

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Applying database migrations..."</span>
.venv/bin/python backend/manage.py migrate --noinput

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Collecting static files..."</span>
.venv/bin/python backend/manage.py collectstatic --noinput
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$SKIP_FRONTEND</span>"</span> = <span class="hljs-literal">false</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Building frontend..."</span>
  ( <span class="hljs-built_in">cd</span> frontend-vue &amp;&amp; npm ci &amp;&amp; npm run build )
<span class="hljs-keyword">else</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Skipping frontend build due to --skip-frontend option"</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Reloading systemd daemon..."</span>
sudo systemctl daemon-reload

services=(
  gunicorn
  celery
  celery-beat
  nginx
  redis-server
  postgresql
)

<span class="hljs-keyword">for</span> service <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${services[@]}</span>"</span>; <span class="hljs-keyword">do</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Restarting <span class="hljs-variable">$service</span>..."</span>
  sudo systemctl restart <span class="hljs-string">"<span class="hljs-variable">$service</span>"</span>
<span class="hljs-keyword">done</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Waiting for services to settle..."</span>
sleep 2

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Services status:"</span>
<span class="hljs-keyword">for</span> service <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${services[@]}</span>"</span>; <span class="hljs-keyword">do</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"===== <span class="hljs-variable">$service</span> ====="</span>
  sudo systemctl status <span class="hljs-string">"<span class="hljs-variable">$service</span>"</span> --no-pager
<span class="hljs-keyword">done</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"All services restarted."</span>
</code></pre>
<p><strong>Save this script as</strong> <code>deploy.sh</code> and make it executable:</p>
<pre><code class="lang-bash">chmod +x deploy.sh
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Fail2Ban Magic: Keeping Your Django Castle Safe 🏰🐾]]></title><description><![CDATA[Imagine your Django server is a cozy castle. Brave knights (your users) come and go, but naughty robots (bad bots) sometimes knock repeatedly on secret doors (login pages) trying to sneak in. We need Fail2Ban, a friendly guard dog, to watch the castl...]]></description><link>https://blog.sorv.dev/fail2ban-magic-keeping-your-django-castle-safe</link><guid isPermaLink="true">https://blog.sorv.dev/fail2ban-magic-keeping-your-django-castle-safe</guid><category><![CDATA[Linux]]></category><category><![CDATA[vps]]></category><category><![CDATA[firewall]]></category><category><![CDATA[Secure Coding Practices]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Wed, 04 Jun 2025 08:26:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/9GkqnLihWQk/upload/0a6ab44f54d131235f74dc0f994d62d6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine your Django server is a cozy castle. Brave knights (your users) come and go, but naughty robots (bad bots) sometimes knock repeatedly on secret doors (login pages) trying to sneak in. We need <strong>Fail2Ban</strong>, a friendly guard dog, to watch the castle logs and chase away any robot that knocks too many times!</p>
<hr />
<h2 id="heading-1-what-is-fail2ban">1. What Is Fail2Ban?</h2>
<ul>
<li><strong>Guard Dog</strong>: Monitors log files like a watchful puppy.  </li>
<li><strong>Jails</strong>: Special zones around each door (service) where the guard listens.  </li>
<li><strong>Filters</strong>: Magic spells (regular expressions) that spot naughty knocks (bad actions).  </li>
<li><strong>Actions</strong>: When a robot misbehaves too much, the guard slams the door shut by adding a firewall rule.</li>
</ul>
<hr />
<h2 id="heading-2-why-use-fail2ban-with-django">2. Why Use Fail2Ban with Django?</h2>
<ol>
<li><strong>Protect the Admin Tower</strong><br />The <code>/admin/</code> page is a treasure room. Don’t let robots guess passwords!  </li>
<li><strong>Guard the Login Gate</strong><br />Your API login endpoint (<code>/auth/login/</code>) needs extra eyes.  </li>
<li><strong>Stop Crawler Floods</strong><br />Bots triggering hundreds of 404 pages can overwhelm the castle.  </li>
<li><strong>Layered Security</strong><br />Compliments your Django <code>settings.py</code> security (SSL redirect, HSTS, X-Frame-Options).</li>
</ol>
<hr />
<h2 id="heading-3-setting-up-fail2ban-step-by-step">3. Setting Up Fail2Ban Step-by-Step</h2>
<h3 id="heading-step-31-install-your-guard">Step 3.1: Install Your Guard</h3>
<pre><code class="lang-bash">sudo apt update
sudo apt install -y ufw fail2ban
</code></pre>
<ul>
<li><strong>UFW</strong>: The castle’s drawbridge controller (firewall).  </li>
<li><strong>Fail2Ban</strong>: The guard dog watching your logs.</li>
</ul>
<h3 id="heading-step-32-configure-the-drawbridge-ufw">Step 3.2: Configure the Drawbridge (UFW)</h3>
<pre><code class="lang-bash">sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH            <span class="hljs-comment"># Let knights in via SSH</span>
sudo ufw allow <span class="hljs-string">'Nginx Full'</span>       <span class="hljs-comment"># Let visitors reach your website (HTTP/HTTPS)</span>
sudo ufw <span class="hljs-built_in">enable</span>
</code></pre>
<h3 id="heading-step-33-create-the-jail-configuration">Step 3.3: Create the Jail Configuration</h3>
<p>Create <code>/etc/fail2ban/jail.local</code> and add:</p>
<pre><code class="lang-ini"><span class="hljs-section">[DEFAULT]</span>
<span class="hljs-attr">bantime</span>  = <span class="hljs-number">3600</span>        <span class="hljs-comment"># Ban duration: 1 hour</span>
<span class="hljs-attr">findtime</span> = <span class="hljs-number">600</span>         <span class="hljs-comment"># Look back: 10 minutes</span>
<span class="hljs-attr">maxretry</span> = <span class="hljs-number">5</span>           <span class="hljs-comment"># After 5 bad tries, ban</span>

<span class="hljs-section">[sshd]</span>
<span class="hljs-attr">enabled</span> = <span class="hljs-literal">true</span>
<span class="hljs-attr">port</span>    = ssh
<span class="hljs-attr">filter</span>  = sshd
<span class="hljs-attr">logpath</span> = /var/log/auth.log

<span class="hljs-section">[django-login]</span>
<span class="hljs-attr">enabled</span>  = <span class="hljs-literal">true</span>
<span class="hljs-attr">port</span>     = http,https
<span class="hljs-attr">filter</span>   = django-login
<span class="hljs-attr">logpath</span>  = /var/log/nginx/access.log
</code></pre>
<ul>
<li><strong><code>bantime</code></strong>: How long the misbehaver is kicked out.  </li>
<li><strong><code>findtime</code></strong>: Time window to count failures.  </li>
<li><strong><code>maxretry</code></strong>: Number of strikes before the ban.</li>
</ul>
<h3 id="heading-step-34-write-your-magic-spell-filter">Step 3.4: Write Your Magic Spell (Filter)</h3>
<p>Create <code>/etc/fail2ban/filter.d/django-login.conf</code>:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Definition]</span>
<span class="hljs-comment"># Spot HTTP 401 responses on /auth/login/</span>
<span class="hljs-attr">failregex</span> = ^&lt;HOST&gt; -.*POST /auth/login/.*<span class="hljs-string">" 401
ignoreregex =</span>
</code></pre>
<p>This tells Fail2Ban: “Whenever you see a 401 Unauthorized on a login POST, count it!”</p>
<h3 id="heading-step-35-start-the-guard-dog">Step 3.5: Start the Guard Dog</h3>
<pre><code class="lang-bash">sudo systemctl restart fail2ban
</code></pre>
<hr />
<h2 id="heading-4-testing-your-guard">4. Testing Your Guard</h2>
<ol>
<li><strong>Check All Jails</strong>  <pre><code class="lang-bash">sudo fail2ban-client status
</code></pre>
</li>
<li><strong>Inspect the Django-Login Jail</strong>  <pre><code class="lang-bash">sudo fail2ban-client status django-login
</code></pre>
</li>
<li><strong>Simulate Robot Knocks</strong>  <pre><code class="lang-bash"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {1..6}; <span class="hljs-keyword">do</span>
  curl -s -o /dev/null -w <span class="hljs-string">"%{http_code}"</span> \
    -X POST https://your-domain.com/auth/login/ \
    -H <span class="hljs-string">"Content-Type: application/json"</span> \
    -d <span class="hljs-string">'{"username":"bad","password":"bad"}'</span>
<span class="hljs-keyword">done</span>
</code></pre>
After 5 “bad” tries, your IP should appear in the banned list!</li>
</ol>
<hr />
<h2 id="heading-5-extra-security-tips">5. Extra Security Tips</h2>
<ul>
<li><strong>Protect Your Logs</strong>: Only root can edit <code>/var/log/*</code> so robots can’t erase evidence.  </li>
<li><strong>Combine with Django Security</strong>: In <code>settings.py</code>, enable:<ul>
<li><code>SECURE_SSL_REDIRECT = True</code></li>
<li>HSTS (<code>SECURE_HSTS_SECONDS</code>)</li>
<li><code>X_FRAME_OPTIONS = "DENY"</code>  </li>
</ul>
</li>
<li><strong>Add More Jails</strong>:<ul>
<li><code>nginx-http-auth</code> (blocks repeated 401 basic-auth failures)  </li>
<li><code>nginx-404</code> (blocks too many broken-page hits)  </li>
<li><code>nginx-limit-req</code> (stops traffic floods)  </li>
</ul>
</li>
<li><strong>Tweak Thresholds</strong>: Adjust <code>bantime</code>, <code>findtime</code>, <code>maxretry</code> based on real traffic.  </li>
<li><strong>Email Alerts</strong>: Use <code>action_mwl</code> to notify you on each ban.</li>
</ul>
<hr />
<h2 id="heading-6-you-did-it">6. You Did It!</h2>
<p>🎉 You now have a trusty Fail2Ban guard watching your Django castle. No more pesky robots breaking in! Keep learning, keep coding, and enjoy your safe server.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Strict Content Security Policy ( CSP ) in Vue.js & Django with Nginx]]></title><description><![CDATA[Introduction
Content Security Policy (CSP) is a security standard that helps prevent cross-site scripting (XSS), clickjacking, and other code-injection attacks by whitelisting trusted sources of content. When correctly configured, CSP ensures that on...]]></description><link>https://blog.sorv.dev/implementing-strict-content-security-policy-csp-in-vuejs-and-django-with-nginx</link><guid isPermaLink="true">https://blog.sorv.dev/implementing-strict-content-security-policy-csp-in-vuejs-and-django-with-nginx</guid><category><![CDATA[Django]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[content security policy]]></category><category><![CDATA[nginx]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Tue, 03 Jun 2025 13:49:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/pqkGQboKSeM/upload/0b08f03efb6589160a951dac6bf3ca3a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Content Security Policy (CSP) is a security standard that helps prevent cross-site scripting (XSS), clickjacking, and other code-injection attacks by whitelisting trusted sources of content. When correctly configured, CSP ensures that only approved scripts, styles, images, and connections can be loaded by the browser.</p>
<h2 id="heading-why-csp-matters">Why CSP Matters</h2>
<p>Without a strict CSP, an attacker could inject malicious scripts into your application, steal user credentials, or redirect users to phishing pages:</p>
<pre><code class="lang-mermaid">flowchart LR
  subgraph Attacker
    A1[Craft malicious payload] --&gt; A2[Host exploit page or social-engineer user]
  end

  subgraph Server
    B1[Receives request with payload]
    B2[Writes to DB or reflects it back]
    B1 --&gt; B2
  end

  subgraph VictimBrowser
    C1[Fetch page from Server]
    C1 --&gt; C2[Parses HTML including attacker’s `&lt;script&gt;`]
    C2 --&gt; C3[Executes malicious JS]
    C3 --&gt; C4[Steals cookies or redirects to phishing site]
  end

  A2 --&gt; B1
  B2 --&gt; C1
</code></pre>
<h2 id="heading-how-csp-works">How CSP Works</h2>
<p>When the browser receives HTML along with a CSP header, it enforces each directive on every resource request:</p>
<pre><code class="lang-mermaid">flowchart TD
  1[Start: Browser requests page] --&gt; 2[Receive HTML + CSP header]
  2 --&gt; 3[Parse HTML]
  3 --&gt; 4{Resource Tag?}
  4 --&gt;|Yes| 5[Identify Resource Type]
  5 --&gt; 6[Lookup CSP directive for that type]
  6 --&gt; 7{URL in whitelist?}
  7 --&gt;|Yes| 8[Allow load]
  7 --&gt;|No| 9[Block load &amp; log warning]
  4 --&gt;|No| 10[Continue rendering]
  8 --&gt; 10
  9 --&gt; 10
  10 --&gt; 11[Page finishes rendering]
  11 --&gt; 12[End]
</code></pre>
<h2 id="heading-browser-enforcement-flow">Browser Enforcement Flow</h2>
<p>This diagram shows how the browser enforces CSP on resources like scripts, styles, images, and XHR/fetch requests:</p>
<pre><code class="lang-mermaid">flowchart LR
  subgraph Server
    A[Serve index.html] --&gt;|Include CSP header| B((HTTP response))
  end

  subgraph Browser
    B --&gt; C{Read CSP header}
    C --&gt; D[Enforce rules on scripts/styles/images/connect]
    D --&gt;|Allowed| E[Load resource]
    D --&gt;|Blocked| F[Drop resource + warn]
  end

  subgraph Page
    E --&gt; G[App JS runs]
    F --&gt; G
  end
</code></pre>
<h2 id="heading-configuring-csp-in-nginx-for-vuejs">Configuring CSP in Nginx for Vue.js</h2>
<p>In your Nginx server block for the Vue.js static build, add a CSP header:</p>
<pre><code class="lang-nginx"><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
    <span class="hljs-attribute">server_name</span> yourdomain.com;

    <span class="hljs-attribute">root</span> /var/www/vue-dist;
    <span class="hljs-attribute">index</span> index.html;

    <span class="hljs-attribute">add_header</span> Content-Security-Policy <span class="hljs-string">"
      default-src 'self';
      script-src 'self' https://cdn.jsdelivr.net;
      style-src 'self' 'unsafe-inline';
      img-src 'self' data:;
      font-src 'self' https://fonts.gstatic.com;
      connect-src 'self' https://api.yourdomain.com;
    "</span> always;

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ /index.html;
    }
}
</code></pre>
<ul>
<li><strong>default-src</strong>: Fallback for all resource types.</li>
<li><strong>script-src</strong>: Whitelist your own bundle and trusted CDNs.</li>
<li><strong>style-src</strong>: Allow inline styles for runtime libraries like Floating-UI.</li>
<li><strong>img-src</strong>: Permit data URIs for base64 images.</li>
<li><strong>font-src</strong>: Load web fonts from trusted providers.</li>
<li><strong>connect-src</strong>: Ensure the SPA can communicate with the Django API.</li>
</ul>
<h2 id="heading-configuring-csp-in-django">Configuring CSP in Django</h2>
<p>Use the <a target="_blank" href="https://github.com/mozilla/django-csp">django-csp</a> middleware or a custom header:</p>
<h3 id="heading-using-django-csp">Using django-csp</h3>
<pre><code class="lang-python"><span class="hljs-comment"># settings.py</span>
MIDDLEWARE = [
    <span class="hljs-comment"># ...</span>
    <span class="hljs-string">'csp.middleware.CSPMiddleware'</span>,
]

CSP_DEFAULT_SRC = (<span class="hljs-string">"'self'"</span>,)
CSP_SCRIPT_SRC  = (<span class="hljs-string">"'self'"</span>, <span class="hljs-string">"https://cdn.jsdelivr.net"</span>)
CSP_STYLE_SRC   = (<span class="hljs-string">"'self'"</span>, <span class="hljs-string">"'unsafe-inline'"</span>)
CSP_IMG_SRC     = (<span class="hljs-string">"'self'"</span>, <span class="hljs-string">"data:"</span>)
CSP_CONNECT_SRC = (<span class="hljs-string">"'self'"</span>, <span class="hljs-string">"https://api.yourdomain.com"</span>)
</code></pre>
<h3 id="heading-custom-middleware">Custom Middleware</h3>
<pre><code class="lang-python"><span class="hljs-comment"># middleware.py</span>
<span class="hljs-keyword">from</span> django.utils.deprecation <span class="hljs-keyword">import</span> MiddlewareMixin

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContentSecurityPolicyMiddleware</span>(<span class="hljs-params">MiddlewareMixin</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_response</span>(<span class="hljs-params">self, request, response</span>):</span>
        policy = (
            <span class="hljs-string">"default-src 'self'; "</span>
            <span class="hljs-string">"script-src 'self' https://cdn.jsdelivr.net; "</span>
            <span class="hljs-string">"style-src 'self' 'unsafe-inline'; "</span>
            <span class="hljs-string">"img-src 'self' data:; "</span>
            <span class="hljs-string">"connect-src 'self' https://api.yourdomain.com;"</span>
        )
        response[<span class="hljs-string">'Content-Security-Policy'</span>] = policy
        <span class="hljs-keyword">return</span> response
</code></pre>
<blockquote>
<p><strong>Note:</strong> If your Django project only serves API endpoints (no HTML responses or templates), you typically don't need to configure CSP on the Django side. Simply enforce CSP at your Nginx or frontend layer.</p>
</blockquote>
<h2 id="heading-inline-script-handling">Inline Script Handling</h2>
<p>When an attacker tries to inject inline <code>&lt;script&gt;</code> tags via URL parameters or form inputs, CSP will block execution:</p>
<pre><code class="lang-mermaid">flowchart LR
  A[Victim visits /page?msg=PAYLOAD] --&gt; B[Browser requests HTML + CSP header]
  B --&gt; C[Browser parses HTML]
  C --&gt; D[Finds inline `&lt;script&gt;` from PAYLOAD]
  D --&gt; E{script-src allows inline?}
  E --&gt;|No| F[Block execution + throw console error]
  E --&gt;|Yes| G[Would execute script]
  F --&gt; H[User safe]
</code></pre>
<h2 id="heading-testing-amp-troubleshooting">Testing &amp; Troubleshooting</h2>
<ol>
<li><strong>Check the browser console</strong> for “Refused to load” errors.</li>
<li><strong>Whitelisting</strong>: Add missing domains in the right directive (e.g., <code>connect-src</code> vs. <code>script-src</code>).</li>
<li><strong>Certificate permissions</strong>: Ensure Nginx can read SSL cert files if using HTTPS.</li>
<li><strong>Disable Rocket Loader</strong> (Cloudflare) if you must avoid <code>'unsafe-inline'</code> for scripts.</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Implementing a strict Content Security Policy via Nginx and Django dramatically reduces the risk of XSS and resource injection attacks. By carefully whitelisting only trusted sources and monitoring violations, you harden both your Vue.js frontend and Django backend against a wide range of threats.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing a Robust SQLite Backup System in Django]]></title><description><![CDATA[In this blog post, I'll share our approach to implementing a comprehensive SQLite backup system for a Django application. This system not only creates backups but also verifies their integrity and automatically pushes them to GitHub for safe storage....]]></description><link>https://blog.sorv.dev/implementing-a-robust-sqlite-backup-system-in-django</link><guid isPermaLink="true">https://blog.sorv.dev/implementing-a-robust-sqlite-backup-system-in-django</guid><category><![CDATA[SQLite]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[django orm]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sat, 07 Dec 2024 11:14:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GNyjCePVRs8/upload/8f5ec5cbc5bc4c4488077c231b4ec95b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this blog post, I'll share our approach to implementing a comprehensive SQLite backup system for a Django application. This system not only creates backups but also verifies their integrity and automatically pushes them to GitHub for safe storage.</p>
<p>we will use sqlite online backup API for this. you can read more about it here <a target="_blank" href="https://www.sqlite.org/backup.html">https://www.sqlite.org/backup.html</a></p>
<blockquote>
<p>The <a target="_blank" href="https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit">Online Backup API was created to a</a>ddress these concerns. The online backup API allows the contents of one database to be copied into another database file, replacing any original contents of the target database. The copy operation may be done incrementally, in which case the source database does not need to be locked for the duration of the copy, only for the brief periods of time when it is actually being read from. This allows other database users to continue without excessive delays while a backup of an online database is made.</p>
</blockquote>
<h2 id="heading-system-components">System Components</h2>
<h3 id="heading-1-backup-creation">1. Backup Creation</h3>
<ul>
<li><p>Uses SQLite's Online Backup API</p>
</li>
<li><p>Creates timestamped backups while the database is in use</p>
</li>
<li><p>Maintains atomic consistency during backup</p>
</li>
<li><p>Stores backups in a configured directory</p>
</li>
</ul>
<p><code>apps/realtime_timer/management/commands/backup_</code><a target="_blank" href="http://database.py"><code>database.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> django.core.management.base <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">import</span> logging

logger = logging.getLogger(__name__)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    <span class="hljs-string">"""
    Django management command to create SQLite database backups.
    Uses SQLite's built-in online backup API which allows creating backups
    while the database is in use.

    Usage:
        python manage.py backup_database

    The backup will be stored in the BACKUP_DIRECTORY specified in settings.SQLITE_BACKUP
    with a timestamp in the filename.
    """</span>

    help = <span class="hljs-string">'Creates a backup of the SQLite database using the online backup API'</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># Create backup directory if it doesn't exist</span>
            <span class="hljs-comment"># Uses the path specified in settings.SQLITE_BACKUP['BACKUP_DIRECTORY']</span>
            backup_dir = os.path.join(settings.BASE_DIR, <span class="hljs-string">'backups'</span>)
            os.makedirs(backup_dir, exist_ok=<span class="hljs-literal">True</span>)

            <span class="hljs-comment"># Create a timestamp for unique backup filename</span>
            <span class="hljs-comment"># Format: YYYYMMDD_HHMMSS</span>
            timestamp = datetime.now().strftime(<span class="hljs-string">'%Y%m%d_%H%M%S'</span>)

            <span class="hljs-comment"># Get the path of the current database from Django settings</span>
            db_path = settings.DATABASES[<span class="hljs-string">'default'</span>][<span class="hljs-string">'NAME'</span>]

            <span class="hljs-comment"># Generate the backup filepath with timestamp</span>
            backup_path = os.path.join(backup_dir, <span class="hljs-string">f'backup_<span class="hljs-subst">{timestamp}</span>.db'</span>)

            <span class="hljs-comment"># Open connection to the source (current) database</span>
            source = sqlite3.connect(db_path)

            <span class="hljs-comment"># Create a new connection for the backup database</span>
            backup = sqlite3.connect(backup_path)

            <span class="hljs-comment"># Use SQLite's backup API to create the backup</span>
            <span class="hljs-comment"># The backup() method is atomic and will maintain consistency</span>
            <span class="hljs-comment"># even if the database is being written to during backup</span>
            <span class="hljs-keyword">with</span> source, backup:
                source.backup(backup)
                logger.info(<span class="hljs-string">f"Database backup created successfully at <span class="hljs-subst">{backup_path}</span>"</span>)
                self.stdout.write(
                    self.style.SUCCESS(<span class="hljs-string">f'Successfully created backup at <span class="hljs-subst">{backup_path}</span>'</span>)
                )

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            logger.error(<span class="hljs-string">f"Database backup failed: <span class="hljs-subst">{str(e)}</span>"</span>)
            self.stdout.write(
                self.style.ERROR(<span class="hljs-string">f'Backup failed: <span class="hljs-subst">{str(e)}</span>'</span>)
            )
</code></pre>
<h3 id="heading-2-backup-management">2. Backup Management</h3>
<ul>
<li><p>Maintains a configurable number of recent backups</p>
</li>
<li><p>Automatically removes older backups</p>
</li>
<li><p>Includes backup rotation policies</p>
</li>
<li><p>Prevents disk space issues</p>
</li>
</ul>
<p><code>apps/realtime_timer/management/commands/manage_backups</code><a target="_blank" href="http://manager.py"><code>.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> glob
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime, timedelta
<span class="hljs-keyword">from</span> django.core.management.base <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> django.core.management <span class="hljs-keyword">import</span> call_command

logger = logging.getLogger(__name__)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    <span class="hljs-string">"""
    Django management command to manage SQLite database backups.
    This command:
    1. Removes old backups exceeding the maximum count
    2. Verifies the integrity of remaining backups

    Usage:
        python manage.py manage_backups

    Configuration is read from settings.SQLITE_BACKUP:
        BACKUP_DIRECTORY: Where backups are stored
        BACKUP_MAX_COUNT: Maximum number of backups to keep
    """</span>

    help = <span class="hljs-string">'Manages SQLite database backups by removing old ones'</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># Get backup settings from Django settings</span>
            backup_dir = settings.SQLITE_BACKUP[<span class="hljs-string">'BACKUP_DIRECTORY'</span>]
            max_backups = settings.SQLITE_BACKUP[<span class="hljs-string">'BACKUP_MAX_COUNT'</span>]

            <span class="hljs-comment"># Get all backup files and sort them by modification time</span>
            <span class="hljs-comment"># newest files first</span>
            backup_files = glob.glob(os.path.join(backup_dir, <span class="hljs-string">'backup_*.db'</span>))
            backup_files.sort(key=os.path.getmtime, reverse=<span class="hljs-literal">True</span>)

            <span class="hljs-comment"># If we have more backups than the maximum allowed</span>
            <span class="hljs-keyword">if</span> len(backup_files) &gt; max_backups:
                <span class="hljs-comment"># Remove the oldest backups (those beyond max_backups)</span>
                <span class="hljs-keyword">for</span> old_backup <span class="hljs-keyword">in</span> backup_files[max_backups:]:
                    os.remove(old_backup)
                    logger.info(<span class="hljs-string">f"Removed old backup: <span class="hljs-subst">{old_backup}</span>"</span>)
                    self.stdout.write(
                        self.style.SUCCESS(<span class="hljs-string">f'Removed old backup: <span class="hljs-subst">{old_backup}</span>'</span>)
                    )

            <span class="hljs-comment"># After cleanup, verify all remaining backups</span>
            <span class="hljs-comment"># This calls the verify_backup command for each backup file</span>
            <span class="hljs-keyword">for</span> backup_file <span class="hljs-keyword">in</span> backup_files[:max_backups]:
                self.stdout.write(<span class="hljs-string">f"Verifying backup: <span class="hljs-subst">{backup_file}</span>"</span>)
                call_command(<span class="hljs-string">'verify_backup'</span>, backup_file=str(backup_file))

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            logger.error(<span class="hljs-string">f"Backup management failed: <span class="hljs-subst">{str(e)}</span>"</span>)
            self.stdout.write(
                self.style.ERROR(<span class="hljs-string">f'Backup management failed: <span class="hljs-subst">{str(e)}</span>'</span>)
            )
</code></pre>
<h3 id="heading-3-backup-verification">3. Backup Verification</h3>
<p>The verification process includes:</p>
<ul>
<li><p>Table structure comparison</p>
</li>
<li><p>Row count validation</p>
</li>
<li><p>Foreign key constraint checks</p>
</li>
<li><p>Test restoration to temporary database</p>
</li>
<li><p>Comprehensive integrity checks</p>
<p>  <code>apps/realtime_timer/management/commands/verify_</code><a target="_blank" href="http://backup.py"><code>backup.py</code></a></p>
<pre><code class="lang-python">  <span class="hljs-keyword">import</span> os
  <span class="hljs-keyword">import</span> sqlite3
  <span class="hljs-keyword">import</span> tempfile
  <span class="hljs-keyword">from</span> django.core.management.base <span class="hljs-keyword">import</span> BaseCommand
  <span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
  <span class="hljs-keyword">from</span> django.apps <span class="hljs-keyword">import</span> apps
  <span class="hljs-keyword">import</span> logging
  <span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path

  logger = logging.getLogger(__name__)

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
      <span class="hljs-string">"""
      Django management command to verify the integrity of SQLite database backups.
      This command:
      1. Creates a temporary database
      2. Restores the backup to this temporary database
      3. Verifies:
         - Table structures match the original
         - Row counts match
         - Foreign key constraints are valid

      Usage:
          python manage.py verify_backup  # verifies latest backup
          python manage.py verify_backup --backup-file=/path/to/backup.db  # verifies specific backup
      """</span>

      help = <span class="hljs-string">'Verifies SQLite backup integrity and tests restoration'</span>

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_arguments</span>(<span class="hljs-params">self, parser</span>):</span>
          <span class="hljs-comment"># Allow specifying a particular backup file to verify</span>
          parser.add_argument(
              <span class="hljs-string">'--backup-file'</span>,
              help=<span class="hljs-string">'Specific backup file to verify. If not provided, verifies latest backup.'</span>
          )

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">verify_table_structure</span>(<span class="hljs-params">self, source_conn, test_conn, table_name</span>):</span>
          <span class="hljs-string">"""
          Compare table schemas between original and restored databases.
          Uses SQLite's sqlite_master table to get the CREATE TABLE statements
          and compares them.
          """</span>
          source_schema = source_conn.execute(<span class="hljs-string">"SELECT sql FROM sqlite_master WHERE type='table' AND name=?"</span>, (table_name,)).fetchone()
          test_schema = test_conn.execute(<span class="hljs-string">"SELECT sql FROM sqlite_master WHERE type='table' AND name=?"</span>, (table_name,)).fetchone()
          <span class="hljs-keyword">return</span> source_schema == test_schema

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">verify_row_counts</span>(<span class="hljs-params">self, source_conn, test_conn, table_name</span>):</span>
          <span class="hljs-string">"""
          Compare the number of rows in each table between original and restored databases.
          A simple COUNT(*) should match if the backup is valid.
          """</span>
          source_count = source_conn.execute(<span class="hljs-string">f"SELECT COUNT(*) FROM <span class="hljs-subst">{table_name}</span>"</span>).fetchone()[<span class="hljs-number">0</span>]
          test_count = test_conn.execute(<span class="hljs-string">f"SELECT COUNT(*) FROM <span class="hljs-subst">{table_name}</span>"</span>).fetchone()[<span class="hljs-number">0</span>]
          <span class="hljs-keyword">return</span> source_count == test_count

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">verify_foreign_keys</span>(<span class="hljs-params">self, conn</span>):</span>
          <span class="hljs-string">"""
          Verify that all foreign key constraints are valid in the restored database.
          Uses SQLite's PRAGMA foreign_key_check which returns empty list if all constraints are valid.
          """</span>
          <span class="hljs-keyword">return</span> conn.execute(<span class="hljs-string">"PRAGMA foreign_key_check"</span>).fetchall() == []

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
          <span class="hljs-keyword">try</span>:
              backup_dir = settings.SQLITE_BACKUP[<span class="hljs-string">'BACKUP_DIRECTORY'</span>]

              <span class="hljs-comment"># Determine which backup file to verify</span>
              <span class="hljs-keyword">if</span> options[<span class="hljs-string">'backup_file'</span>]:
                  backup_path = Path(options[<span class="hljs-string">'backup_file'</span>])
                  <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> backup_path.exists():
                      <span class="hljs-keyword">raise</span> FileNotFoundError(<span class="hljs-string">f"Backup file not found: <span class="hljs-subst">{backup_path}</span>"</span>)
              <span class="hljs-keyword">else</span>:
                  <span class="hljs-comment"># If no specific file provided, get the most recent backup</span>
                  backup_files = sorted(
                      Path(backup_dir).glob(<span class="hljs-string">'backup_*.db'</span>),
                      key=<span class="hljs-keyword">lambda</span> x: x.stat().st_mtime,
                      reverse=<span class="hljs-literal">True</span>
                  )
                  <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> backup_files:
                      <span class="hljs-keyword">raise</span> FileNotFoundError(<span class="hljs-string">"No backup files found"</span>)
                  backup_path = backup_files[<span class="hljs-number">0</span>]

              <span class="hljs-comment"># Create a temporary database file for testing restoration</span>
              <span class="hljs-keyword">with</span> tempfile.NamedTemporaryFile(suffix=<span class="hljs-string">'.db'</span>, delete=<span class="hljs-literal">False</span>) <span class="hljs-keyword">as</span> temp_db:
                  temp_db_path = temp_db.name

              <span class="hljs-keyword">try</span>:
                  <span class="hljs-comment"># Connect to both backup and temporary database</span>
                  backup_conn = sqlite3.connect(str(backup_path))
                  test_conn = sqlite3.connect(temp_db_path)

                  <span class="hljs-comment"># Restore the backup to temporary database</span>
                  <span class="hljs-keyword">with</span> backup_conn, test_conn:
                      backup_conn.backup(test_conn)

                  <span class="hljs-comment"># Get all table names from Django models</span>
                  django_tables = [
                      model._meta.db_table
                      <span class="hljs-keyword">for</span> model <span class="hljs-keyword">in</span> apps.get_models()
                  ]

                  <span class="hljs-comment"># Store verification results</span>
                  verification_results = {
                      <span class="hljs-string">'structure'</span>: [],  <span class="hljs-comment"># Table structure verification results</span>
                      <span class="hljs-string">'row_counts'</span>: [], <span class="hljs-comment"># Row count verification results</span>
                      <span class="hljs-string">'foreign_keys'</span>: <span class="hljs-literal">True</span>  <span class="hljs-comment"># Foreign key constraint verification</span>
                  }

                  <span class="hljs-comment"># Verify each table</span>
                  <span class="hljs-keyword">for</span> table <span class="hljs-keyword">in</span> django_tables:
                      <span class="hljs-comment"># Check if table structures match</span>
                      structure_match = self.verify_table_structure(backup_conn, test_conn, table)
                      verification_results[<span class="hljs-string">'structure'</span>].append({
                          <span class="hljs-string">'table'</span>: table,
                          <span class="hljs-string">'matches'</span>: structure_match
                      })

                      <span class="hljs-comment"># Check if row counts match</span>
                      count_match = self.verify_row_counts(backup_conn, test_conn, table)
                      verification_results[<span class="hljs-string">'row_counts'</span>].append({
                          <span class="hljs-string">'table'</span>: table,
                          <span class="hljs-string">'matches'</span>: count_match
                      })

                  <span class="hljs-comment"># Verify foreign key constraints</span>
                  verification_results[<span class="hljs-string">'foreign_keys'</span>] = self.verify_foreign_keys(test_conn)

                  <span class="hljs-comment"># Check if all verifications passed</span>
                  all_passed = (
                      all(r[<span class="hljs-string">'matches'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> verification_results[<span class="hljs-string">'structure'</span>]) <span class="hljs-keyword">and</span>
                      all(r[<span class="hljs-string">'matches'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> verification_results[<span class="hljs-string">'row_counts'</span>]) <span class="hljs-keyword">and</span>
                      verification_results[<span class="hljs-string">'foreign_keys'</span>]
                  )

                  <span class="hljs-keyword">if</span> all_passed:
                      <span class="hljs-comment"># All checks passed - backup is valid</span>
                      logger.info(<span class="hljs-string">f"Backup verification successful for <span class="hljs-subst">{backup_path}</span>"</span>)
                      self.stdout.write(
                          self.style.SUCCESS(<span class="hljs-string">f'Backup verification successful for <span class="hljs-subst">{backup_path}</span>'</span>)
                      )
                  <span class="hljs-keyword">else</span>:
                      <span class="hljs-comment"># Collect all failed checks for detailed error reporting</span>
                      failed_checks = []
                      <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> verification_results[<span class="hljs-string">'structure'</span>]:
                          <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> result[<span class="hljs-string">'matches'</span>]:
                              failed_checks.append(<span class="hljs-string">f"Structure mismatch in table <span class="hljs-subst">{result[<span class="hljs-string">'table'</span>]}</span>"</span>)

                      <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> verification_results[<span class="hljs-string">'row_counts'</span>]:
                          <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> result[<span class="hljs-string">'matches'</span>]:
                              failed_checks.append(<span class="hljs-string">f"Row count mismatch in table <span class="hljs-subst">{result[<span class="hljs-string">'table'</span>]}</span>"</span>)

                      <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> verification_results[<span class="hljs-string">'foreign_keys'</span>]:
                          failed_checks.append(<span class="hljs-string">"Foreign key constraints validation failed"</span>)

                      logger.error(<span class="hljs-string">f"Backup verification failed for <span class="hljs-subst">{backup_path}</span>: <span class="hljs-subst">{<span class="hljs-string">', '</span>.join(failed_checks)}</span>"</span>)
                      self.stdout.write(
                          self.style.ERROR(<span class="hljs-string">f'Backup verification failed: <span class="hljs-subst">{<span class="hljs-string">", "</span>.join(failed_checks)}</span>'</span>)
                      )

              <span class="hljs-keyword">finally</span>:
                  <span class="hljs-comment"># Clean up: remove temporary database</span>
                  <span class="hljs-keyword">if</span> os.path.exists(temp_db_path):
                      os.unlink(temp_db_path)

          <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
              logger.error(<span class="hljs-string">f"Backup verification failed: <span class="hljs-subst">{str(e)}</span>"</span>)
              self.stdout.write(
                  self.style.ERROR(<span class="hljs-string">f'Backup verification failed: <span class="hljs-subst">{str(e)}</span>'</span>)
              )
</code></pre>
</li>
</ul>
<h3 id="heading-4-github-integration">4. GitHub Integration</h3>
<ul>
<li><p>Automatic commits with readable timestamps</p>
</li>
<li><p>Configurable Git user settings</p>
</li>
<li><p>Push to dedicated backup branch</p>
</li>
<li><p>Error handling and logging</p>
</li>
</ul>
<p><code>apps/realtime_timer/management/commands/backup_git_</code><a target="_blank" href="http://push.py"><code>push.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> subprocess
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> django.core.management.base <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">import</span> logging

logger = logging.getLogger(__name__)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    <span class="hljs-string">"""
    Django management command to commit and push database backups to GitHub.
    This command:
    1. Stages all new backup files
    2. Creates a commit with timestamp
    3. Pushes to the configured remote branch

    Usage:
        python manage.py backup_git_push

    Required Environment Variables:
        GIT_USER_NAME: Git user name for commits
        GIT_USER_EMAIL: Git user email for commits
        GIT_BRANCH: Branch to push backups (default: backup)
    """</span>

    help = <span class="hljs-string">'Commits and pushes database backups to GitHub'</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_arguments</span>(<span class="hljs-params">self, parser</span>):</span>
        parser.add_argument(
            <span class="hljs-string">'--force'</span>,
            action=<span class="hljs-string">'store_true'</span>,
            help=<span class="hljs-string">'Skip confirmation prompt and force push backup'</span>,
        )

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">execute_git_command</span>(<span class="hljs-params">self, command, error_message</span>):</span>
        <span class="hljs-string">"""Execute git command and handle errors"""</span>
        <span class="hljs-keyword">try</span>:
            result = subprocess.run(
                command,
                cwd=settings.BASE_DIR,
                check=<span class="hljs-literal">True</span>,
                capture_output=<span class="hljs-literal">True</span>,
                text=<span class="hljs-literal">True</span>
            )
            <span class="hljs-keyword">return</span> result.stdout.strip()
        <span class="hljs-keyword">except</span> subprocess.CalledProcessError <span class="hljs-keyword">as</span> e:
            logger.error(<span class="hljs-string">f"<span class="hljs-subst">{error_message}</span>: <span class="hljs-subst">{e.stderr}</span>"</span>)
            <span class="hljs-keyword">raise</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        <span class="hljs-keyword">try</span>:
            backup_dir = settings.SQLITE_BACKUP[<span class="hljs-string">'BACKUP_DIRECTORY'</span>]
            git_user = settings.GIT_USER_NAME
            git_email = settings.GIT_USER_EMAIL

            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> all([git_user, git_email]):
                <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"GIT_USER_NAME and GIT_USER_EMAIL must be set in environment"</span>)

            <span class="hljs-comment"># Configure git user for this commit</span>
            self.execute_git_command(
                [<span class="hljs-string">'git'</span>, <span class="hljs-string">'config'</span>, <span class="hljs-string">'user.name'</span>, git_user],
                <span class="hljs-string">"Failed to set git user name"</span>
            )
            self.execute_git_command(
                [<span class="hljs-string">'git'</span>, <span class="hljs-string">'config'</span>, <span class="hljs-string">'user.email'</span>, git_email],
                <span class="hljs-string">"Failed to set git user email"</span>
            )

            <span class="hljs-comment"># Add confirmation prompt unless --force flag is used</span>
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> options[<span class="hljs-string">'force'</span>]:
                confirm = input(
                    <span class="hljs-string">"\nWARNING: You are about to push database backups to a remote repository.\n"</span>
                    <span class="hljs-string">"This may override existing data in the repository.\n"</span>
                    <span class="hljs-string">"Are you sure you want to continue? [y/N]: "</span>
                ).lower()
                <span class="hljs-keyword">if</span> confirm <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> [<span class="hljs-string">'y'</span>, <span class="hljs-string">'yes'</span>]:
                    self.stdout.write(self.style.WARNING(<span class="hljs-string">'Backup push cancelled'</span>))
                    <span class="hljs-keyword">return</span>

            <span class="hljs-comment"># Stage all backup files</span>
            backup_pattern = os.path.join(backup_dir, <span class="hljs-string">'backup_*.db'</span>)
            self.execute_git_command(
                [<span class="hljs-string">'git'</span>, <span class="hljs-string">'add'</span>, backup_pattern],
                <span class="hljs-string">"Failed to stage backup files"</span>
            )

            <span class="hljs-comment"># Create commit with timestamp</span>
            <span class="hljs-comment"># this time is in UTC+0 </span>
            timestamp = datetime.now().strftime(<span class="hljs-string">'%B %d, %Y %I:%M %p'</span>)
            commit_message = <span class="hljs-string">f"Database backup - <span class="hljs-subst">{timestamp}</span>"</span>
            self.execute_git_command(
                [<span class="hljs-string">'git'</span>, <span class="hljs-string">'commit'</span>, <span class="hljs-string">'-m'</span>, commit_message],
                <span class="hljs-string">"Failed to create commit"</span>
            )

            <span class="hljs-comment"># Push to remote</span>
            self.execute_git_command(
                [<span class="hljs-string">'git'</span>, <span class="hljs-string">'push'</span>, <span class="hljs-string">'origin'</span>],
                <span class="hljs-string">"Failed to push to GitHub"</span>
            )

            logger.info(<span class="hljs-string">"Successfully pushed backup to GitHub"</span>)
            self.stdout.write(
                self.style.SUCCESS(<span class="hljs-string">'Successfully pushed backup to GitHub'</span>)
            )

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            logger.error(<span class="hljs-string">f"Failed to push backup to GitHub: <span class="hljs-subst">{str(e)}</span>"</span>)
            self.stdout.write(
                self.style.ERROR(<span class="hljs-string">'Failed to push backup to GitHub'</span>)
            )
</code></pre>
<h3 id="heading-5-centralized-management">5. Centralized Management</h3>
<p>A single command orchestrates:</p>
<ul>
<li><p>Backup creation</p>
</li>
<li><p>Rotation management</p>
</li>
<li><p>Integrity verification</p>
</li>
<li><p>GitHub synchronization</p>
</li>
</ul>
<p><code>apps/realtime_timer/management/commands/backup_</code><a target="_blank" href="http://manager.py"><code>manager.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> django.core.management.base <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> django.core.management <span class="hljs-keyword">import</span> call_command
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime

logger = logging.getLogger(__name__)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    <span class="hljs-string">"""
    Django management command to handle all backup-related operations:
    1. Creates a new backup
    2. Manages backup retention (removes old backups)
    3. Verifies backup integrity
    4. Pushes backups to GitHub

    Usage:
        python manage.py backup_manager
    """</span>

    help = <span class="hljs-string">'Manages all backup operations (create, verify, rotate, and push to GitHub)'</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        <span class="hljs-keyword">try</span>:
            timestamp = datetime.now().strftime(<span class="hljs-string">'%B %d, %Y %I:%M %p'</span>)
            self.stdout.write(<span class="hljs-string">f"Starting backup operations at <span class="hljs-subst">{timestamp}</span>"</span>,style_func=self.style.WARNING)

            <span class="hljs-comment"># Step 1: Create new backup</span>
            self.stdout.write(<span class="hljs-string">"Creating new backup..."</span>,style_func=self.style.WARNING)
            call_command(<span class="hljs-string">'backup_database'</span>)

            <span class="hljs-comment"># Step 2: Manage backups (includes verification)</span>
            self.stdout.write(<span class="hljs-string">"Managing and verifying backups..."</span>,style_func=self.style.WARNING)
            call_command(<span class="hljs-string">'manage_backups'</span>)

            <span class="hljs-comment"># Step 3: Push to GitHub</span>
            self.stdout.write(<span class="hljs-string">"Pushing backups to GitHub..."</span>,style_func=self.style.WARNING)
            call_command(<span class="hljs-string">'backup_git_push'</span>)

            self.stdout.write(
                self.style.SUCCESS(<span class="hljs-string">'All backup operations completed successfully'</span>)
            )

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            logger.error(<span class="hljs-string">f"Backup operations failed: <span class="hljs-subst">{str(e)}</span>"</span>)
            self.stdout.write(
                self.style.ERROR(<span class="hljs-string">f'Backup operations failed: <span class="hljs-subst">{str(e)}</span>'</span>)
            )
</code></pre>
<h2 id="heading-configuration">Configuration</h2>
<h3 id="heading-settings-in-django">Settings in Django</h3>
<pre><code class="lang-python"><span class="hljs-comment"># SQLite backup settings</span>
SQLITE_BACKUP = {
    <span class="hljs-string">'BACKUP_DIRECTORY'</span>: os.path.join(BASE_DIR, <span class="hljs-string">'backups'</span>),
    <span class="hljs-string">'BACKUP_MAX_COUNT'</span>: <span class="hljs-number">10</span>,  <span class="hljs-comment"># Maximum number of backups to keep</span>
}

GIT_USER_NAME = os.environ.get(<span class="hljs-string">'GIT_USER_NAME'</span>)
GIT_USER_EMAIL = os.environ.get(<span class="hljs-string">'GIT_USER_EMAIL'</span>)
</code></pre>
<ul>
<li><p><code>GIT_USER_NAME</code>: Git username for commits</p>
</li>
<li><p><code>GIT_USER_EMAIL</code>: Git email for commits</p>
</li>
</ul>
<h2 id="heading-automation">Automation</h2>
<h3 id="heading-cron-job-setup">Cron Job Setup</h3>
<p>Run backups every 6 hours:</p>
<pre><code class="lang-bash">0 */6 * * * <span class="hljs-built_in">cd</span> /home/normal_user/focus-timer-v/ &amp;&amp; . /home/normal_user/focus-timer-v/.env &amp;&amp; /home/normal_user/focus-timer-v/venv/bin/python manage.py backup_manager &gt;&gt; /home/normal_user/focus-timer-v/logs/crontab_logs.txt 2&gt;&amp;1; <span class="hljs-built_in">echo</span> <span class="hljs-string">"Cron ran at <span class="hljs-subst">$(date)</span>"</span> &gt;&gt; /home/normal_user/focus-timer-v/logs/debug_cron.log
</code></pre>
<p>Here is an screenshot showing it in action 👇🏻</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733633273756/1f7d8f80-0611-426c-becf-bb2c16b7ca5b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This backup system provides a robust, automated solution for SQLite database backups in Django applications. It ensures data safety through multiple verification layers and maintains backup integrity while requiring minimal manual intervention.</p>
]]></content:encoded></item><item><title><![CDATA[Case Study: Building a Real-Time Focus Timer with Django, Redis, and WebSockets]]></title><description><![CDATA[Hey there! Today, we're diving into the inner workings of Tymr, a cloud-based focus timer that's got some interesting tech under the hood. You can check it out at https://tymr.online if you're curious. Let's break down how this app keeps you focused,...]]></description><link>https://blog.sorv.dev/case-study-building-a-real-time-focus-timer-with-django-redis-and-websockets</link><guid isPermaLink="true">https://blog.sorv.dev/case-study-building-a-real-time-focus-timer-with-django-redis-and-websockets</guid><category><![CDATA[Django]]></category><category><![CDATA[Django channels]]></category><category><![CDATA[django orm]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Redis]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sun, 13 Oct 2024 17:07:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/rBPOfVqROzY/upload/c7645f9cb29b6aba560d0ec80eef3c0b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey there! Today, we're diving into the inner workings of Tymr, a cloud-based focus timer that's got some interesting tech under the hood. You can check it out at <a target="_blank" href="https://tymr.online">https://tymr.online</a> if you're curious. Let's break down how this app keeps you focused, even when your browser decides to take a nap.</p>
<h2 id="heading-the-big-picture-how-timer-ticks">The Big Picture: How Timer Ticks</h2>
<p>The <a target="_blank" href="https://tymr.online">Tymr focus</a> timer application uses a unique combination of Redis, Django Channels, and client-side JavaScript to manage real-time timer updates. At its core, a Redis Scheduler continuously checks for scheduled cycle changes. When a change is due, it triggers an update in the Django application, which then uses Django Channels to push this update to connected WebSocket clients. On the client side, a combination of JavaScript and Django template language renders the timer interface and handles user interactions. These interactions are sent back to the Django application via WebSocket, which may result in new cycle schedules being added to Redis. All persistent data, including user information and session details, is stored in a SQLite3 database. This architecture allows for precise, server-side timer management while providing a responsive, real-time experience for users.</p>
<p>Here's a diagram illustrating this architecture:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728837693408/db9604eb-9332-4fd7-a2fb-bfc17d238ceb.png" alt class="image--center mx-auto" /></p>
<p>This diagram shows the flow of data and interactions between the different components of the Tymr application, highlighting the central role of the Redis Scheduler and Django Channels Consumer in managing the focus timer sessions.</p>
<p>Your browser talks to Nginx, our trusty web server.</p>
<ol>
<li><p>Nginx routes HTTP requests to Django and WebSocket connections to Uvicorn (our ASGI server).</p>
</li>
<li><p>Both Django and Uvicorn interact with our SQLite3 database (yep, keeping it simple!) and Redis.</p>
</li>
<li><p>Our Redis Scheduler keeps an eye on when to change the cycle. we just add the cycle details along with focus session id to the redis zset &amp; in the redis_scheduler.py, we keep on scanning the redis zset if there is any data and we process them if found.</p>
</li>
<li><p>OneSignal handles push notifications when your focus session ends.</p>
</li>
<li><p>when a session start, consumer send the timer update to clients &amp; client browsers use web worker to keep the timer ticking. one there is an update about next cycle, that also sent to client browser using websocket connection.</p>
</li>
</ol>
<p>Now, let's zoom in on some of the cool parts!</p>
<p>the decision to use sqlite3 was to keep things simple. i don’t think i need postgresql for this so far. also i saw a short clip by dhh inspiring me for simplicity in the start and not caring much about scalability because we are not there yet. although i worked on this project mainly for learning purpose &amp; experimenting different things i learn.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.linkedin.com/embed/feed/update/urn:li:ugcPost:7245439432672083968">https://www.linkedin.com/embed/feed/update/urn:li:ugcPost:7245439432672083968</a></div>
<p> </p>
<h2 id="heading-battling-browser-naps-with-web-workers">Battling Browser Naps with Web Workers</h2>
<p>Ever noticed how sometimes your browser tabs seem to doze off when you're not looking at them? This can mess with JavaScript timers, which is not great for a focus timer app. To combat this, we're using Web Workers. These little champions run in the background, keeping our timer ticking even when the browser tab is sleeping.</p>
<p>Here's a snippet of how we set this up:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In focus_session.js</span>
<span class="hljs-keyword">const</span> worker = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'hack_timer_worker.js'</span>);

worker.onmessage = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) </span>{
    <span class="hljs-comment">// Handle timer updates</span>
};

<span class="hljs-comment">// Start a timer</span>
worker.postMessage({
    <span class="hljs-attr">name</span>: <span class="hljs-string">'setInterval'</span>,
    <span class="hljs-attr">time</span>: <span class="hljs-number">1000</span>  <span class="hljs-comment">// Update every second</span>
});
</code></pre>
<p>This keeps our timer accurate, even if you switch tabs or minimize your browser. Pretty neat, huh? but to make it more simple, i just add this script before loading the timer related code. <a target="_blank" href="https://github.com/turuslan/HackTimer">https://github.com/turuslan/HackTimer</a> which solves most of tab sleep issues.</p>
<h2 id="heading-redis-scheduler-the-time-lord-of-our-app">Redis Scheduler: The Time Lord of Our App</h2>
<p>Now, let's talk about our Redis Scheduler. This is the behind-the-scenes magician that makes sure your focus cycles change at the right time, even if you close your browser entirely.</p>
<p>Here's how it works:</p>
<ol>
<li><p>When you start a session, we schedule the next cycle change in Redis using a sorted set (zset).</p>
</li>
<li><p>Our Redis Scheduler (running as a Django management command) constantly checks for due changes.</p>
</li>
<li><p>When it's time, it triggers the cycle change and schedules the next one.</p>
</li>
</ol>
<p>Let's look at some code:</p>
<pre><code class="lang-python"><span class="hljs-comment"># In redis_scheduler.py</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisScheduler</span>:</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            now = time.time()
            due_changes = <span class="hljs-keyword">await</span> self.redis.zrangebyscore(<span class="hljs-string">"scheduled_cycle_changes"</span>, <span class="hljs-number">0</span>, now)
            <span class="hljs-keyword">for</span> session_id <span class="hljs-keyword">in</span> due_changes:
                <span class="hljs-keyword">await</span> self.process_change(session_id)
            <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">1</span>)

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_change</span>(<span class="hljs-params">self, session_id</span>):</span>
        session = <span class="hljs-keyword">await</span> selectors.get_session_by_id_async(session_id)
        timer_service = AsyncTimerService(session_id, session.owner, session.owner.username)
        <span class="hljs-keyword">await</span> timer_service.change_cycle_if_needed(session)
        <span class="hljs-keyword">await</span> self.redis.zrem(<span class="hljs-string">"scheduled_cycle_changes"</span>, session_id)
        <span class="hljs-keyword">await</span> timer_service.schedule_next_cycle_change(self.redis)
</code></pre>
<p>This setup allows for precise timing without relying solely on client-side JavaScript. Cool, right? well here is the complete redis_scheduler.py file incase you wanna know.  </p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> redis.asyncio <span class="hljs-keyword">as</span> redis
<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">from</span> channels.layers <span class="hljs-keyword">import</span> get_channel_layer
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> django.core.management.base <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> apps.realtime_timer.business_logic <span class="hljs-keyword">import</span> selectors
<span class="hljs-keyword">from</span> apps.realtime_timer.business_logic.services <span class="hljs-keyword">import</span> AsyncTimerService
<span class="hljs-keyword">from</span> apps.realtime_timer.models <span class="hljs-keyword">import</span> FocusSession

logger = logging.getLogger(__name__)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisScheduler</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.redis = redis.Redis.from_url(<span class="hljs-string">f"redis://<span class="hljs-subst">{settings.REDIS_HOST}</span>:<span class="hljs-subst">{settings.REDIS_PORT}</span>"</span>)
        self.channel_layer = get_channel_layer()

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            <span class="hljs-keyword">try</span>:
                now = time.time()
                due_changes = <span class="hljs-keyword">await</span> self.redis.zrangebyscore(<span class="hljs-string">"scheduled_cycle_changes"</span>, <span class="hljs-number">0</span>, now, start=<span class="hljs-number">0</span>, num=<span class="hljs-number">100</span>)
                <span class="hljs-keyword">if</span> due_changes:
                    <span class="hljs-keyword">for</span> session_id <span class="hljs-keyword">in</span> due_changes:
                        logger.info(<span class="hljs-string">"--------------------------------------------"</span>)
                        <span class="hljs-keyword">await</span> self.process_change(session_id)
                        logger.info(<span class="hljs-string">"--------------------------------------------"</span>)
                    <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> remove this wait if possible</span>
                    <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">0.1</span>)
                <span class="hljs-keyword">else</span>:
                    spinner = <span class="hljs-string">"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"</span>
                    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(<span class="hljs-number">10</span>):
                        print(<span class="hljs-string">f"\rWaiting for changes <span class="hljs-subst">{spinner[_ % len(spinner)]}</span>"</span>, end=<span class="hljs-string">""</span>, flush=<span class="hljs-literal">True</span>)
                        <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">0.1</span>)
                    print(<span class="hljs-string">"\r"</span> + <span class="hljs-string">" "</span> * <span class="hljs-number">30</span> + <span class="hljs-string">"\r"</span>, end=<span class="hljs-string">""</span>, flush=<span class="hljs-literal">True</span>)
                    <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">1</span>)
            <span class="hljs-keyword">except</span> redis.RedisError <span class="hljs-keyword">as</span> e:
                logger.error(<span class="hljs-string">f"Redis error: <span class="hljs-subst">{e}</span>"</span>)
                <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">5</span>)
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                logger.error(<span class="hljs-string">f"Unexpected error: <span class="hljs-subst">{e}</span>"</span>)
                <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">5</span>)

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_change</span>(<span class="hljs-params">self, session_id</span>):</span>
        <span class="hljs-keyword">try</span>:
            session_id = session_id.decode()
            session = <span class="hljs-keyword">await</span> selectors.get_session_by_id_async(session_id)
            session_owner = <span class="hljs-keyword">await</span> selectors.get_session_owner_async(session)
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> session:
                logger.error(<span class="hljs-string">f"Session <span class="hljs-subst">{session_id}</span> not found, skipping processing"</span>)
                <span class="hljs-keyword">await</span> self.redis.zrem(<span class="hljs-string">"scheduled_cycle_changes"</span>, session_id)
                <span class="hljs-keyword">return</span>
            timer_service = AsyncTimerService(session_id, session_owner, session_owner.username)
            <span class="hljs-keyword">if</span> session.timer_state == FocusSession.TIMER_RUNNING:
                logger.info(<span class="hljs-string">f"calling timer_service.change_cycle_if_needed for session <span class="hljs-subst">{session_id}</span>"</span>)
                <span class="hljs-keyword">await</span> timer_service.change_cycle_if_needed(session)
                logger.info(<span class="hljs-string">f"calling timer_service.schedule_next_cycle_change for session <span class="hljs-subst">{session_id}</span>"</span>)
                <span class="hljs-keyword">await</span> self.redis.zrem(<span class="hljs-string">"scheduled_cycle_changes"</span>, session_id)
                <span class="hljs-keyword">await</span> timer_service.schedule_next_cycle_change(self.redis)
                logger.info(<span class="hljs-string">f"cycle change completed for session <span class="hljs-subst">{session_id}</span>"</span>)
            <span class="hljs-keyword">else</span>:
                logger.info(<span class="hljs-string">f"session <span class="hljs-subst">{session_id}</span> is not running, skipping cycle change"</span>)
                <span class="hljs-keyword">await</span> self.redis.zrem(<span class="hljs-string">"scheduled_cycle_changes"</span>, session_id)
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            <span class="hljs-keyword">await</span> self.redis.zrem(<span class="hljs-string">"scheduled_cycle_changes"</span>, session_id)
            logger.error(<span class="hljs-string">f"Error processing change for session <span class="hljs-subst">{session_id}</span>: <span class="hljs-subst">{e}</span>"</span>)
            logger.exception(<span class="hljs-string">"Full traceback:"</span>)


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    help = <span class="hljs-string">"Runs the Redis scheduler for focus session cycle changes"</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        scheduler = RedisScheduler()
        asyncio.run(scheduler.run())
</code></pre>
<h2 id="heading-race-conditions-when-timing-is-everything">Race Conditions: When Timing is Everything</h2>
<p>Now, let's talk about a tricky problem in concurrent programming: race conditions. Imagine two people trying to update the same focus session at the same time. Chaos, right?</p>
<p>We faced this issue with our focus cycles. Multiple processes could try to update the same session simultaneously, leading to:</p>
<ul>
<li><p>Incorrect cycle transitions</p>
</li>
<li><p>Wrong focus period calculations</p>
</li>
<li><p>Inconsistent timer states</p>
</li>
</ul>
<p>To tackle this, we use Django's <code>select_for_update()</code>. It's like putting a "Do Not Disturb" sign on our database rows. Here's how we use it:</p>
<pre><code class="lang-python"><span class="hljs-meta">@database_sync_to_async</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_current_cycle_locked_async</span>(<span class="hljs-params">session: FocusSession</span>):</span>
    <span class="hljs-keyword">return</span> FocusCycle.objects.select_for_update().get(id=session.current_cycle_id)
</code></pre>
<p>But be careful! Using <code>select_for_update()</code> can lead to deadlocks if not used properly. Here are some precautions:</p>
<ol>
<li><p>Always acquire locks in the same order across your application.</p>
</li>
<li><p>Keep the locked section as short as possible.</p>
</li>
<li><p>Use timeouts to prevent indefinite waiting.</p>
</li>
</ol>
<p>For example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> transaction

<span class="hljs-meta">@database_sync_to_async</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_session</span>(<span class="hljs-params">session_id</span>):</span>
    <span class="hljs-keyword">with</span> transaction.atomic():
        session = FocusSession.objects.select_for_update(nowait=<span class="hljs-literal">True</span>).get(id=session_id)
        <span class="hljs-comment"># Perform updates here</span>
        session.save()
</code></pre>
<p>The <code>nowait=True</code> option helps prevent deadlocks by raising an error immediately if the lock can't be acquired and you can catch that error and handle those cases manually.</p>
<h2 id="heading-async-consumers-real-time-magic">Async Consumers: Real-Time Magic</h2>
<p>Last but not least, let's chat about our async consumers. These are the multitasking ninjas of our application, handling real-time communications without breaking a sweat.</p>
<p>We use Django Channels to handle WebSocket connections asynchronously. This allows us to manage multiple user connections simultaneously. Here's a simplified version of our consumer:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FocusSessionConsumer</span>(<span class="hljs-params">AsyncWebsocketConsumer</span>):</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connect</span>(<span class="hljs-params">self</span>):</span>
        self.session_id = self.scope[<span class="hljs-string">"url_route"</span>][<span class="hljs-string">"kwargs"</span>][<span class="hljs-string">"session_id"</span>]
        self.session_group_name = <span class="hljs-string">f"focus_session_<span class="hljs-subst">{self.session_id}</span>"</span>
        <span class="hljs-keyword">await</span> self.channel_layer.group_add(self.session_group_name, self.channel_name)
        <span class="hljs-keyword">await</span> self.accept()

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">receive</span>(<span class="hljs-params">self, text_data</span>):</span>
        data = json.loads(text_data)
        <span class="hljs-keyword">if</span> data[<span class="hljs-string">"action"</span>] == <span class="hljs-string">"toggle_timer"</span>:
            <span class="hljs-keyword">await</span> self.toggle_timer()
        <span class="hljs-comment"># ................................</span>

<span class="hljs-meta">    @check_session_owner_async</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">toggle_timer</span>(<span class="hljs-params">self</span>):</span>
        timer_state = <span class="hljs-keyword">await</span> self.timer_service.toggle_timer()
        <span class="hljs-keyword">if</span> timer_state == <span class="hljs-string">"paused"</span>:
            <span class="hljs-keyword">await</span> self.timer_service.cancel_scheduled_cycle_change_if_timer_stopped(self.redis_client, self.session_id)
        <span class="hljs-keyword">elif</span> timer_state == <span class="hljs-string">"resumed"</span>:
            <span class="hljs-keyword">await</span> self.timer_service.schedule_next_cycle_change(redis_client=self.redis_client)

    <span class="hljs-comment"># rest of the logic...................</span>
</code></pre>
<p>This setup allows us to handle real-time updates for multiple users smoothly!</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Building Tymr was like assembling a high-tech clock with a sprinkle of cloud magic. From battling browser naps with Web Workers to orchestrating time with our Redis Scheduler, and managing real-time connections with async consumers - each component plays a crucial role in keeping everything ticking smoothly.</p>
<p>I deployed this django app to https://tymr.online so incase you want to learn about deployment of Django app on vps then check this link</p>
<p><a target="_blank" href="https://selftaughtdev.hashnode.dev/comprehensive-django-deployment-guide-for-beginners">https://selftaughtdev.hashnode.dev/comprehensive-django-deployment-guide-for-beginners</a></p>
<p>Got any questions or want to dive deeper into any part? Drop a comment below - I'd love to geek out about it with you!</p>
<p>p.s. I am still learning &amp; i may have missed some points here or not explain them well.</p>
]]></content:encoded></item><item><title><![CDATA[Comprehensive Django Deployment Guide for Beginners]]></title><description><![CDATA[Table of Contents

Introduction

Prerequisites

Setting Up Your VPS

Securing Your Server

Installing Required Software

Configuring Your Django Project

Setting Up Nginx and Uvicorn

Configuring SSL with Let's Encrypt

Setting Up Supervisor

Firewal...]]></description><link>https://blog.sorv.dev/comprehensive-django-deployment-guide-for-beginners</link><guid isPermaLink="true">https://blog.sorv.dev/comprehensive-django-deployment-guide-for-beginners</guid><category><![CDATA[#django deployment]]></category><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[VPS Hosting]]></category><category><![CDATA[django orm]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sun, 13 Oct 2024 15:18:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/PSpf_XgOM5w/upload/821735f4311ee6c2edec192b085ac40a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p>Introduction</p>
</li>
<li><p>Prerequisites</p>
</li>
<li><p>Setting Up Your VPS</p>
</li>
<li><p>Securing Your Server</p>
</li>
<li><p>Installing Required Software</p>
</li>
<li><p>Configuring Your Django Project</p>
</li>
<li><p>Setting Up Nginx and Uvicorn</p>
</li>
<li><p>Configuring SSL with Let's Encrypt</p>
</li>
<li><p>Setting Up Supervisor</p>
</li>
<li><p>Firewall Configuration</p>
</li>
<li><p>Deploying Your Django Application</p>
</li>
<li><p>Running the Redis Scheduler</p>
</li>
<li><p>Monitoring and Maintenance</p>
</li>
<li><p>Troubleshooting</p>
</li>
<li><p>Conclusion</p>
</li>
</ol>
<h2 id="heading-1-introduction">1. Introduction</h2>
<p>This guide will walk you through the process of deploying a Django application with ASGI (Uvicorn) to a Virtual Private Server (VPS). We'll also cover setting up a Redis scheduler, configuring Nginx as a reverse proxy, securing your server with a firewall, and enabling HTTPS. This guide is designed for beginners, so we'll explain each step in detail.</p>
<h2 id="heading-2-prerequisites">2. Prerequisites</h2>
<p>Before we begin, make sure you have:</p>
<ul>
<li><p>A VPS running Ubuntu 22.04</p>
</li>
<li><p>A domain name (e.g., <a target="_blank" href="http://tymr.online">tymr.online</a>) pointed to your VPS's IP address</p>
</li>
<li><p>Your Django project code (including requirements.txt)</p>
</li>
<li><p>Basic familiarity with command-line interfaces</p>
</li>
</ul>
<h2 id="heading-3-setting-up-your-vps">3. Setting Up Your VPS</h2>
<p>First, we need to access our VPS. We'll use SSH (Secure Shell) for this.</p>
<pre><code class="lang-bash">ssh root@your_server_ip
</code></pre>
<p>Replace <code>your_server_ip</code> with your VPS's IP address. You'll be prompted for a password, which you should have received from your VPS provider or in most VPS you can directly login into server after pasting the local desktop’s ssh public key into the server during the setup process. for example, in digital ocean you have this feature here while creating a droplet ( VPS ) 👇</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728391180535/dc512bf1-20b6-422b-93ae-a3863047ca59.png" alt class="image--center mx-auto" /></p>
<p>Once logged in, update your system:</p>
<pre><code class="lang-bash">sudo apt update
sudo apt upgrade -y
</code></pre>
<p>These commands update the list of available packages and upgrade them to their latest versions.</p>
<h2 id="heading-4-securing-your-server">4. Securing Your Server</h2>
<h3 id="heading-creating-a-new-user">Creating a New User</h3>
<p>It's best practice to avoid using the root user. Let's create a new user:</p>
<pre><code class="lang-bash">adduser username
</code></pre>
<p>Replace <code>username</code> with your desired username. You'll be prompted to set a password and provide some optional information.</p>
<p>Grant this user sudo privileges:</p>
<pre><code class="lang-bash">usermod -aG sudo username
</code></pre>
<p>This command adds the user to the sudo group, allowing them to run commands with superuser privileges.</p>
<h3 id="heading-setting-up-ssh-key-authentication">Setting Up SSH Key Authentication</h3>
<p>SSH keys are more secure than passwords. On your local machine, generate an SSH key pair:</p>
<pre><code class="lang-bash">ssh-keygen -t rsa -b 4096
</code></pre>
<p>This creates a public/private key pair. The public key can be freely shared, while the private key must be kept secret.</p>
<p>Copy the public key to your server:</p>
<pre><code class="lang-bash">ssh-copy-id username@your_server_ip
</code></pre>
<p>Now, let's disable password authentication for SSH. On the server:</p>
<pre><code class="lang-bash">sudo nano /etc/ssh/sshd_config
</code></pre>
<p>Find and modify these lines:</p>
<pre><code class="lang-python">PasswordAuthentication no
PermitRootLogin no
</code></pre>
<p>Save the file (Ctrl+X, then Y, then Enter) and restart the SSH service:</p>
<pre><code class="lang-bash">sudo systemctl restart sshd
</code></pre>
<p><strong><em>Now login as non root user you just created.</em></strong></p>
<h2 id="heading-5-installing-required-software">5. Installing Required Software</h2>
<p>Let's install the necessary software:</p>
<pre><code class="lang-bash">sudo apt install build-essential python3-dev python3.11 python3.11-venv python3-pip nginx redis
</code></pre>
<p>This installs Python 3.11, pip (Python package manager), and Nginx (web server).</p>
<h2 id="heading-6-configuring-your-django-project">6. Configuring Your Django Project</h2>
<p>Create a directory for your project:</p>
<pre><code class="lang-bash">mkdir ~/myproject
<span class="hljs-built_in">cd</span> ~/myproject
</code></pre>
<p>Upload your project files to this directory. You can use SCP, SFTP, or Git for this.</p>
<p>Create a virtual environment:</p>
<pre><code class="lang-bash">python3.11 -m venv venv
<span class="hljs-built_in">source</span> venv/bin/activate
</code></pre>
<p>Install your project dependencies:</p>
<pre><code class="lang-bash">pip install -r requirements.txt
</code></pre>
<p>Also, install Uvicorn:</p>
<pre><code class="lang-bash">pip install uvicorn
</code></pre>
<h3 id="heading-understanding-and-setting-up-user-permissions-for-our-django-project"><strong>Understanding and Setting Up User Permissions for our Django project</strong></h3>
<p>When deploying a Django application, proper file permissions and ownership are crucial for security and functionality. Here's a detailed guide tailored for beginners:</p>
<h3 id="heading-understanding-users-and-groups">Understanding Users and Groups</h3>
<p>In our setup, we'll work with three main entities:</p>
<ol>
<li><p>Your user account (e.g., 'normaluser')</p>
</li>
<li><p>The web server user (usually 'www-data' for Nginx on Ubuntu)</p>
</li>
<li><p>The root user</p>
</li>
</ol>
<h3 id="heading-best-practices-for-django-project-files">Best Practices for Django Project Files</h3>
<ol>
<li><p>Project Directory Ownership: Your user should own the project directory and most files.</p>
<pre><code class="lang-bash"> sudo chown -R normaluser:normaluser /home/normaluser/myproject
</code></pre>
</li>
<li><p>Project Directory Permissions: Set 755 (drwxr-xr-x) for directories and 644 (-rw-r--r--) for files.</p>
<pre><code class="lang-bash"> find /home/normaluser/myproject -<span class="hljs-built_in">type</span> d -<span class="hljs-built_in">exec</span> chmod 755 {} \;
 find /home/normaluser/myproject -<span class="hljs-built_in">type</span> f -<span class="hljs-built_in">exec</span> chmod 644 {} \;
</code></pre>
</li>
<li><p>Sensitive Files: For files like <a target="_blank" href="http://settings.py"><code>settings.py</code></a>, use more restrictive permissions.</p>
<pre><code class="lang-bash"> chmod 600 /home/normaluser/myproject/myproject/settings.py
</code></pre>
</li>
<li><p>Static and Media Directories: The web server needs read access to these.</p>
<pre><code class="lang-bash"> sudo chown -R normaluser:www-data /home/normaluser/myproject/static
 sudo chown -R normaluser:www-data /home/normaluser/myproject/media
 sudo chmod -R 750 /home/normaluser/myproject/static
 sudo chmod -R 750 /home/normaluser/myproject/media
</code></pre>
</li>
<li><p>Log Directory: Create a logs directory within your project and set appropriate permissions.</p>
<pre><code class="lang-bash"> mkdir -p /home/normaluser/myproject/logs
 sudo chown -R normaluser:www-data /home/normaluser/myproject/logs
 sudo chmod -R 770 /home/normaluser/myproject/logs
</code></pre>
</li>
<li><p>SQLite Database (if used): If you're using SQLite, secure the database file.</p>
<pre><code class="lang-bash"> sudo chown normaluser:www-data /home/normaluser/myproject/db.sqlite3
 sudo chmod 660 /home/normaluser/myproject/db.sqlite3
</code></pre>
</li>
<li><p>Virtual Environment: Keep your virtual environment secure.</p>
<pre><code class="lang-bash"> chmod -R 750 /home/normaluser/myproject/venv
</code></pre>
</li>
</ol>
<h3 id="heading-explanation-of-permissions">Explanation of Permissions</h3>
<ul>
<li><p>755 for directories: Owner can read/write/execute, others can read/execute.</p>
</li>
<li><p>644 for regular files: Owner can read/write, others can read.</p>
</li>
<li><p>600 for sensitive files: Only owner can read/write.</p>
</li>
<li><p>750 for static/media: Owner can read/write/execute, group (www-data) can read/execute.</p>
</li>
<li><p>770 for logs: Owner and group can read/write/execute.</p>
</li>
<li><p>660 for SQLite: Owner and group can read/write.</p>
</li>
</ul>
<h3 id="heading-security-best-practices">Security Best Practices</h3>
<ol>
<li><p>Principle of Least Privilege: Give only the permissions necessary for each file and directory.</p>
</li>
<li><p>Use a Non-Root User: Always run your Django application as a non-root user.</p>
</li>
<li><p>Separate Media Uploads: If possible, store user-uploaded files outside of the project directory.</p>
</li>
<li><p>Regular Audits: Periodically check and update file permissions.</p>
</li>
<li><p>Use Environment Variables: Store sensitive information like secret keys in environment variables, not in files.</p>
</li>
<li><p>Secure the Secret Key: Ensure Django's SECRET_KEY is not in version control and has restricted access.</p>
</li>
<li><p>Backup Permissions: When backing up, preserve file permissions.</p>
</li>
</ol>
<h3 id="heading-applying-changes">Applying Changes</h3>
<p>After setting permissions, restart your web server and Django application:</p>
<pre><code class="lang-bash">sudo systemctl restart nginx
sudo supervisorctl restart all
</code></pre>
<h3 id="heading-verifying-permissions">Verifying Permissions</h3>
<p>You can verify permissions using the <code>ls -l</code> command. For example:</p>
<pre><code class="lang-bash">ls -l /home/normaluser/myproject
</code></pre>
<p>This will show you the permissions for files and directories in your project root.</p>
<p>Remember, these settings are a starting point. You may need to adjust based on your specific setup and security requirements. Always test thoroughly after changing permissions to ensure your application functions correctly.</p>
<h2 id="heading-7-nginx-configuration">7. Nginx Configuration</h2>
<p>Before we set up our Nginx configuration, let's remove the default configuration:</p>
<pre><code class="lang-bash">sudo rm /etc/nginx/sites-enabled/default
</code></pre>
<p>Now, let's create our configuration file:</p>
<pre><code class="lang-bash">sudo nano /etc/nginx/sites-available/myproject
</code></pre>
<p>Add the following content:</p>
<pre><code class="lang-nginx"><span class="hljs-section">server</span> {
    <span class="hljs-comment"># replace your domain names with placeholder tymr.online</span>
    <span class="hljs-attribute">server_name</span> tymr.online www.tymr.online;  <span class="hljs-comment"># Your domain name(s)</span>

    <span class="hljs-attribute">location</span> = /favicon.ico { 
        <span class="hljs-attribute">access_log</span> <span class="hljs-literal">off</span>; 
        <span class="hljs-attribute">log_not_found</span> <span class="hljs-literal">off</span>; 
    }  <span class="hljs-comment"># Efficiently handle favicon requests</span>

    <span class="hljs-attribute">location</span> /static/ {
        <span class="hljs-attribute">root</span> /home/username/myproject;  <span class="hljs-comment"># Path to your static files</span>
    }  <span class="hljs-comment"># Serve static files directly</span>

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-attribute">proxy_pass</span> http://unix:/home/username/myproject/myproject.sock;  <span class="hljs-comment"># Pass requests to Uvicorn</span>
        <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;
        <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>;
        <span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">"upgrade"</span>;
        <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$proxy_add_x_forwarded_for</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;
    }  <span class="hljs-comment"># Proxy settings for Uvicorn</span>
}
</code></pre>
<p>Let's break this down:</p>
<ul>
<li><p><code>server_name</code>: Specifies which domain names this server block should respond to.</p>
</li>
<li><p><code>location = /favicon.ico</code>: Efficiently handles favicon requests.</p>
</li>
<li><p><code>location /static/</code>: Configures Nginx to serve static files directly, without passing the request to Django.</p>
</li>
<li><p><code>location /</code>: The main block that handles all other requests.</p>
<ul>
<li><p><code>proxy_pass</code>: Forwards requests to our Uvicorn server.</p>
</li>
<li><p>The <code>proxy_set_header</code> directives: Pass necessary headers to the Django application.</p>
</li>
</ul>
</li>
</ul>
<p>After creating this file, enable it:</p>
<pre><code class="lang-bash">sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
</code></pre>
<p>This creates a symbolic link, effectively enabling our configuration.</p>
<p>Test the Nginx configuration:</p>
<pre><code class="lang-bash">sudo nginx -t
</code></pre>
<p>If it's okay, restart Nginx:</p>
<pre><code class="lang-bash">sudo systemctl restart nginx
</code></pre>
<h2 id="heading-8-setting-up-ssl-with-lets-encrypt">8. Setting Up SSL with Let's Encrypt</h2>
<p>We'll use Snap to install Certbot, which automates the process of obtaining and renewing Let's Encrypt certificates.</p>
<ol>
<li><p>Ensure Snap is up-to-date:</p>
<pre><code class="lang-bash"> sudo snap install core; sudo snap refresh core
</code></pre>
<p> This installs and updates the core snap package.</p>
</li>
<li><p>Remove any old Certbot installations:</p>
<pre><code class="lang-bash"> sudo apt remove certbot
</code></pre>
</li>
<li><p>Install Certbot via Snap:</p>
<pre><code class="lang-bash"> sudo snap install --classic certbot
</code></pre>
<p> The <code>--classic</code> flag allows Certbot to access system resources outside of the snap's confined environment.</p>
</li>
<li><p>Create a symbolic link for easy access:</p>
<pre><code class="lang-bash"> sudo ln -s /snap/bin/certbot /usr/bin/certbot
</code></pre>
</li>
<li><p>Obtain and install the certificate:</p>
<pre><code class="lang-bash"> sudo certbot --nginx -d tymr.online -d www.tymr.online
</code></pre>
<p> This command will:</p>
<ul>
<li><p>Obtain a certificate for your domains</p>
</li>
<li><p>Automatically configure Nginx to use the new certificate</p>
</li>
<li><p>Set up automatic renewal</p>
</li>
</ul>
</li>
</ol>
<p>Follow the prompts during the Certbot execution. It will ask for your email and agreement to terms of service.</p>
<p>For more detailed information, you can refer to this similar guide: <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04">How To Secure Nginx with Let's Encrypt on Ubuntu 22.04</a></p>
<h2 id="heading-9-configuring-supervisor">9. Configuring Supervisor</h2>
<p>Supervisor is a process control system that allows you to monitor and control a number of processes on UNIX-like operating systems. We use Supervisor because:</p>
<ol>
<li><p>It automatically restarts processes if they crash.</p>
</li>
<li><p>It starts processes on system boot.</p>
</li>
<li><p>It provides an easy way to start, stop, and monitor processes.</p>
</li>
<li><p>It logs stdout and stderr of the processes it manages.</p>
</li>
</ol>
<p>Here's how to set it up:</p>
<ol>
<li><p>Install Supervisor:</p>
<pre><code class="lang-bash"> sudo apt install supervisor
</code></pre>
</li>
<li><p>Create a configuration file:</p>
<pre><code class="lang-bash"> sudo nano /etc/supervisor/conf.d/myproject.conf
</code></pre>
</li>
<li><p>Add the following content:</p>
</li>
</ol>
<pre><code class="lang-ini"><span class="hljs-section">[program:myproject]</span>
<span class="hljs-attr">command</span>=/home/username/myproject/venv/bin/uvicorn myproject.asgi:application --unix-socket /home/username/myproject/myproject.sock
<span class="hljs-attr">directory</span>=/home/username/myproject
<span class="hljs-attr">user</span>=username
<span class="hljs-attr">autostart</span>=<span class="hljs-literal">true</span>
<span class="hljs-attr">autorestart</span>=<span class="hljs-literal">true</span>
<span class="hljs-attr">stderr_logfile</span>=/var/log/myproject.err.log
<span class="hljs-attr">stdout_logfile</span>=/var/log/myproject.out.log

<span class="hljs-section">[program:celery]</span>
<span class="hljs-attr">command</span>=/home/username/myproject/venv/bin/celery -A myproject worker -l info
<span class="hljs-attr">directory</span>=/home/username/myproject
<span class="hljs-attr">user</span>=username
<span class="hljs-attr">autostart</span>=<span class="hljs-literal">true</span>
<span class="hljs-attr">autorestart</span>=<span class="hljs-literal">true</span>
<span class="hljs-attr">stderr_logfile</span>=/var/log/celery.err.log
<span class="hljs-attr">stdout_logfile</span>=/var/log/celery.out.log

<span class="hljs-section">[program:redis_scheduler]</span>
<span class="hljs-attr">command</span>=/home/username/myproject/venv/bin/python manage.py redis_scheduler
<span class="hljs-attr">directory</span>=/home/username/myproject
<span class="hljs-attr">user</span>=username
<span class="hljs-attr">autostart</span>=<span class="hljs-literal">true</span>
<span class="hljs-attr">autorestart</span>=<span class="hljs-literal">true</span>
<span class="hljs-attr">stderr_logfile</span>=/var/log/redis_scheduler.err.log
<span class="hljs-attr">stdout_logfile</span>=/var/log/redis_scheduler.out.log
</code></pre>
<p>This configuration sets up three processes:</p>
<ul>
<li><p>Your Django application running with Uvicorn</p>
</li>
<li><p>A Celery worker for background tasks</p>
</li>
<li><p>Your custom Redis scheduler</p>
</li>
</ul>
<ol start="4">
<li><p>Reload Supervisor to apply changes:</p>
<pre><code class="lang-bash"> sudo supervisorctl reread
 sudo supervisorctl update
</code></pre>
</li>
</ol>
<p>Supervisor is different from simple systemd services because it provides more fine-grained control and is specifically designed for managing application processes, making it ideal for complex setups like Django projects with multiple components.</p>
<h2 id="heading-10-firewall-configuration">10. Firewall Configuration</h2>
<p>UFW (Uncomplicated Firewall) is a user-friendly interface for managing iptables, the default firewall management tool for Ubuntu.</p>
<p>Enable UFW:</p>
<pre><code class="lang-bash">sudo ufw <span class="hljs-built_in">enable</span>
</code></pre>
<p>Allow necessary connections:</p>
<pre><code class="lang-bash">sudo ufw allow ssh
sudo ufw allow <span class="hljs-string">'Nginx Full'</span>
</code></pre>
<p>These commands allow SSH connections and both HTTP and HTTPS traffic for Nginx.</p>
<p>Check the status:</p>
<pre><code class="lang-bash">sudo ufw status
</code></pre>
<h3 id="heading-final-list-of-ufw-commands">Final List of UFW Commands:</h3>
<pre><code class="lang-bash">sudo ufw allow OpenSSH <span class="hljs-comment"># You need to allow SSH so you can access your VPS remotely.</span>
sudo ufw allow <span class="hljs-string">'Nginx HTTP'</span>
sudo ufw allow <span class="hljs-string">'Nginx HTTPS'</span>
sudo ufw allow 8000  <span class="hljs-comment"># Optional, for development server</span>
sudo ufw <span class="hljs-built_in">enable</span>
sudo ufw status
</code></pre>
<h2 id="heading-11-deploying-your-django-application">11. Deploying Your Django Application</h2>
<p>Collect static files:</p>
<pre><code class="lang-bash">python manage.py collectstatic
</code></pre>
<p>Apply migrations:</p>
<pre><code class="lang-bash">python manage.py migrate
</code></pre>
<h2 id="heading-12-running-the-redis-scheduler">12. Running the Redis Scheduler</h2>
<p>The Redis scheduler should now be managed by Supervisor. You can check its status with:</p>
<pre><code class="lang-bash">sudo supervisorctl status
</code></pre>
<h2 id="heading-13-custom-scripts-for-process-management">13. Custom Scripts for Process Management</h2>
<p>To simplify starting and stopping all processes, you can create custom scripts. Here are examples:</p>
<ol>
<li><p>Create a start script:</p>
<pre><code class="lang-bash"> nano ~/start_all.sh
</code></pre>
<p> Add the following content:</p>
<pre><code class="lang-bash"> <span class="hljs-comment">#!/bin/bash</span>
 sudo supervisorctl start all
 sudo systemctl start nginx
</code></pre>
</li>
<li><p>Create a stop script:</p>
<pre><code class="lang-bash"> nano ~/stop_all.sh
</code></pre>
<p> Add the following content:</p>
<pre><code class="lang-bash"> <span class="hljs-comment">#!/bin/bash</span>
 sudo supervisorctl stop all
 sudo systemctl stop nginx
</code></pre>
</li>
<li><p>Make these scripts executable:</p>
<pre><code class="lang-bash"> chmod +x ~/start_all.sh ~/stop_all.sh
</code></pre>
</li>
</ol>
<p>Now you can start all processes with <code>./start_</code><a target="_blank" href="http://all.sh"><code>all.sh</code></a> and stop them with <code>./stop_</code><a target="_blank" href="http://all.sh"><code>all.sh</code></a>.</p>
<p>Remember to run these scripts with sudo if your user doesn't have the necessary permissions.</p>
<h2 id="heading-14-monitoring-and-maintenance">14. Monitoring and Maintenance</h2>
<p>Regularly update your system:</p>
<pre><code class="lang-bash">sudo apt update
sudo apt upgrade
</code></pre>
<p>Monitor your logs:</p>
<pre><code class="lang-bash">tail -f /var/<span class="hljs-built_in">log</span>/myproject.err.log
tail -f /var/<span class="hljs-built_in">log</span>/myproject.out.log
tail -f /var/<span class="hljs-built_in">log</span>/redis_scheduler.err.log
tail -f /var/<span class="hljs-built_in">log</span>/redis_scheduler.out.log
</code></pre>
<h2 id="heading-15-troubleshooting">15. Troubleshooting</h2>
<ul>
<li><p>If your site isn't accessible, check Nginx status: <code>sudo systemctl status nginx</code></p>
</li>
<li><p>If Uvicorn isn't running, check Supervisor: <code>sudo supervisorctl status</code></p>
</li>
<li><p>Check Nginx error logs: <code>sudo tail -f /var/log/nginx/error.log</code></p>
</li>
<li><p>Ensure your firewall isn't blocking connections: <code>sudo ufw status</code></p>
</li>
</ul>
<h2 id="heading-16-conclusion">16. Conclusion</h2>
<p>You've now deployed your Django application with Uvicorn, set up Nginx as a reverse proxy, configured SSL, and set up a Redis scheduler. Your application should be accessible via HTTPS at your domain.</p>
<p>Remember to keep your system and applications updated, monitor your logs, and maintain good security practices.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering Complex DataTables with Django: A Deep Dive into Server-Side Processing]]></title><description><![CDATA[Hey fellow Django enthusiasts! Today, I'm excited to share a recent challenge I tackled involving Django and DataTables. This one's a bit complex, so grab your favorite caffeinated beverage and let's dive in!
but first, here is a screencast of how it...]]></description><link>https://blog.sorv.dev/mastering-complex-datatables-with-django-a-deep-dive-into-server-side-processing</link><guid isPermaLink="true">https://blog.sorv.dev/mastering-complex-datatables-with-django-a-deep-dive-into-server-side-processing</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Datatables]]></category><category><![CDATA[django orm]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sat, 05 Oct 2024 04:01:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/eveI7MOcSmw/upload/62339fffedefb1833e3aa2bbfdbe5188.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey fellow Django enthusiasts! Today, I'm excited to share a recent challenge I tackled involving Django and DataTables. This one's a bit complex, so grab your favorite caffeinated beverage and let's dive in!</p>
<p>but first, here is a screencast of how it looks like in action when i implemented it my project. i have replace the project code with some generic example code because i can’t share the project code as it’s confidential.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://x.com/saurav__codes/status/1842547549498155508">https://x.com/saurav__codes/status/1842547549498155508</a></div>
<p> </p>
<p>The Challenge: your company have a blog management system that required displaying a table of blog posts with various attributes. they need server-side processing to handle potentially thousands of posts efficiently, with dynamic columns based on user permissions, and complex sorting and filtering capabilities.</p>
<p>The Solution: Let's break this down step-by-step:</p>
<ol>
<li>Setting Up the Frontend: First, I set up DataTables on the client side:</li>
</ol>
<pre><code class="lang-javascript"><span class="hljs-comment">// This setup enables server-side processing </span>
$(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> host_url = <span class="hljs-built_in">window</span>.location.origin;
  <span class="hljs-keyword">var</span> dataTable = $(<span class="hljs-string">'#blog-posts-table'</span>).DataTable({
    <span class="hljs-attr">serverSide</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">sAjaxSource</span>: <span class="hljs-string">`<span class="hljs-subst">${host_url}</span>/api/blog-posts-data`</span>,
    <span class="hljs-attr">paging</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">processing</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-comment">// disables ordering on the last two columns (action buttons).</span>
    <span class="hljs-attr">columnDefs</span>: [
      { <span class="hljs-attr">orderable</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">targets</span>: <span class="hljs-number">-1</span> },
      { <span class="hljs-attr">orderable</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">targets</span>: <span class="hljs-number">-2</span> }
    ]
  });
});
</code></pre>
<ol start="2">
<li>Django View Magic: The real complexity lay in the Django view. Here's the complete view.</li>
</ol>
<pre><code class="lang-python"><span class="hljs-meta">@login_required</span>
<span class="hljs-meta">@user_passes_test(lambda u: u.has_perm("view_blog_posts"))</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">blog_posts_datatables_api_view</span>(<span class="hljs-params">request</span>):</span>
    request_data = request.GET.copy()
    <span class="hljs-comment"># we need to return the value of draw in int format</span>
    <span class="hljs-comment"># it is strongly recommended for security reasons that </span>
    <span class="hljs-comment"># you cast this parameter to an integer, rather than simply </span>
    <span class="hljs-comment"># echoing back to the client what it sent in the draw parameter, </span>
    <span class="hljs-comment"># in order to prevent Cross Site Scripting (XSS) attacks</span>
    request_data[<span class="hljs-string">"draw"</span>] = int(request_data.get(<span class="hljs-string">"draw"</span>, <span class="hljs-number">1</span>))
    user = request.user
    blog = Blog.objects.for_user(user)
    approved_categories = get_approved_categories(request, blog)

    post_columns = _get_post_columns(blog)
    order_by_list = _get_col_list_for_ordering(request.GET, post_columns)

    search_query = request.GET.get(<span class="hljs-string">"sSearch"</span>)
    entries_per_page = int(request.GET.get(<span class="hljs-string">"iDisplayLength"</span>, <span class="hljs-number">10</span>))
    entries_start = int(request.GET.get(<span class="hljs-string">"iDisplayStart"</span>, <span class="hljs-number">0</span>))

    search_db_query = Q()
    <span class="hljs-keyword">if</span> search_query:
        search_db_query = (
            Q(author__first_name__icontains=search_query)
            | Q(author__last_name__icontains=search_query)
            | Q(title__icontains=search_query)
            | Q(content__icontains=search_query)
        )

    posts = (
        blog.blogpost_set.filter(
            deleted_on__isnull=<span class="hljs-literal">True</span>,
            draft=<span class="hljs-literal">False</span>,
            author__is_active=<span class="hljs-literal">True</span>,
            category__in=approved_categories,
        )
        .select_related(<span class="hljs-string">"author"</span>, <span class="hljs-string">"category"</span>)
        .order_by(*order_by_list)
    )

    request_data[<span class="hljs-string">"recordsTotal"</span>] = posts.count()

    <span class="hljs-keyword">if</span> search_query:
        posts = posts.filter(search_db_query)

    request_data[<span class="hljs-string">"recordsFiltered"</span>] = posts.count()

    posts = posts[entries_start : entries_start + entries_per_page]

    data = []
    <span class="hljs-keyword">for</span> post <span class="hljs-keyword">in</span> posts:
        _post_data = []
        _author_link = <span class="hljs-string">""</span>
        <span class="hljs-keyword">if</span> blog.show_author_id:
            <span class="hljs-keyword">if</span> post.author.id:
                _author_link = <span class="hljs-string">f'&lt;a href="<span class="hljs-subst">{post.author.get_absolute_url()}</span>"&gt;<span class="hljs-subst">{post.author.id}</span>&lt;/a&gt;'</span>
                _post_data.append(_author_link)
                _post_data.append(post.author.get_full_name())

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> _author_link:
            _post_data.append(<span class="hljs-string">f'&lt;a href="<span class="hljs-subst">{post.author.get_absolute_url()}</span>"&gt;<span class="hljs-subst">{post.author.get_full_name()}</span>&lt;/a&gt;'</span>)

        _post_data.append(post.title)
        _post_data.append(post.category.__str__())
        _post_data.append(post.published_date.strftime(<span class="hljs-string">"%Y-%m-%d %H:%M:%S"</span>))
        _post_data.append(post.view_count)
        _post_data.append(yesno(post.comments_enabled, <span class="hljs-string">"Enabled,Disabled"</span>))

        edit_url = reverse(
            <span class="hljs-string">"blog:post-update"</span>,
            kwargs={<span class="hljs-string">"post_id"</span>: post.id},
        )
        _post_data.append(<span class="hljs-string">f'&lt;a href="<span class="hljs-subst">{edit_url}</span>"&gt;&lt;span class="material-symbols-outlined md-18"&gt;edit&lt;/span&gt;&lt;/a&gt;'</span>)

        toggle_comments_url = reverse(
            <span class="hljs-string">"blog:toggle-post-comments"</span>,
            kwargs={<span class="hljs-string">"post_id"</span>: post.id},
        )
        comments_text = yesno(post.comments_enabled, <span class="hljs-string">"comments_disabled,comments"</span>)
        title = <span class="hljs-string">"Disable Comments"</span> <span class="hljs-keyword">if</span> post.comments_enabled <span class="hljs-keyword">else</span> <span class="hljs-string">"Enable Comments"</span>
        _post_data.append(
            <span class="hljs-string">f'&lt;a href="<span class="hljs-subst">{toggle_comments_url}</span>"&gt;&lt;span title="<span class="hljs-subst">{title}</span>" class="material-symbols-outlined"&gt;<span class="hljs-subst">{comments_text}</span>&lt;/span&gt;&lt;/a&gt;'</span>
        )

        data.append(_post_data)

    request_data[<span class="hljs-string">"data"</span>] = data
    <span class="hljs-keyword">return</span> JsonResponse(request_data)
</code></pre>
<p>Key Points:</p>
<ul>
<li><p>We're copying the request data and modifying it throughout the view.</p>
</li>
<li><p>Dynamic column handling based on blog settings.</p>
</li>
<li><p>Complex filtering using Q objects.</p>
</li>
<li><p>Efficient querying with select_related.</p>
</li>
<li><p>Pagination handled server-side.</p>
</li>
<li><p>Custom data formatting for each post, including HTML for action buttons.</p>
</li>
</ul>
<p>Now there are few params sent in request by client &amp; here is the info about them.</p>
<p>Here's the formatted table in Markdown:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Parameter name</td><td>Type</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>draw</code></td><td>integer</td><td>The draw counter that this object is a response to - from the <code>draw</code> parameter sent as part of the data request. Note that it is <strong>strongly recommended for security reasons</strong> that you <em>cast</em> this parameter to an integer, rather than simply echoing back to the client what it sent in the <code>draw</code> parameter, in order to prevent Cross Site Scripting (XSS) attacks.</td></tr>
<tr>
<td><code>recordsTotal</code></td><td>integer</td><td>Total records, before filtering (i.e. the total number of records in the database)</td></tr>
<tr>
<td><code>recordsFiltered</code></td><td>integer</td><td>Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this page of data).</td></tr>
<tr>
<td><code>data</code></td><td>array</td><td>The data to be displayed in the table. This is an array of data source objects, one for each row, which will be used by DataTables. Note that this parameter's name can be changed using the <code>ajax</code> option's <code>dataSrc</code> property.</td></tr>
<tr>
<td><code>error</code></td><td>string</td><td><em>Optional</em>: If an error occurs during the running of the server-side processing script, you can inform the user of this error by passing back the error message to be displayed using this parameter. Do not include if there is no error.</td></tr>
</tbody>
</table>
</div><p>there are other params also which you can read in detail here - <a target="_blank" href="https://datatables.net/manual/server-side#Returned-data">https://datatables.net/manual/server-side#Returned-data</a></p>
<ol start="3">
<li>Helper Functions: I created helper functions to keep the main view clean:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_post_columns</span>(<span class="hljs-params">blog</span>):</span>
    post_columns = []
    <span class="hljs-keyword">if</span> blog.show_author_id:
        post_columns.append(<span class="hljs-string">"author__id"</span>)
    post_columns.extend([
        <span class="hljs-string">"author__first_name"</span>,
        <span class="hljs-string">"title"</span>,
        <span class="hljs-string">"category"</span>,
        <span class="hljs-string">"published_date"</span>,
        <span class="hljs-string">"view_count"</span>,
        <span class="hljs-string">"comments_enabled"</span>
    ])
    <span class="hljs-keyword">return</span> post_columns

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_col_list_for_ordering</span>(<span class="hljs-params">request_data, post_columns</span>):</span>
    order_by_list = []
    <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> request_data.items():
        <span class="hljs-keyword">if</span> key.startswith(<span class="hljs-string">"iSortCol_"</span>):
            col_index = value
            col_name_key = <span class="hljs-string">f"mDataProp_<span class="hljs-subst">{col_index}</span>"</span>
            col_name = request_data.get(col_name_key)
            col_name = post_columns[int(col_name)]
            sort_dir_key = <span class="hljs-string">f'sSortDir_<span class="hljs-subst">{key.split(<span class="hljs-string">"_"</span>)[<span class="hljs-number">1</span>]}</span>'</span>
            sort_dir = request_data.get(sort_dir_key, <span class="hljs-string">"asc"</span>)
            <span class="hljs-keyword">if</span> sort_dir == <span class="hljs-string">"desc"</span>:
                col_name = <span class="hljs-string">f"-<span class="hljs-subst">{col_name}</span>"</span>
            order_by_list.append(col_name)
    <span class="hljs-keyword">return</span> order_by_list <span class="hljs-keyword">or</span> [post_columns[<span class="hljs-number">0</span>]]
</code></pre>
<p>This function creates a list of columns to use in the queryset's <code>order_by()</code> method:</p>
<ul>
<li><p>It iterates through the request data (which comes from DataTables).</p>
</li>
<li><p>It looks for keys starting with "iSortCol_", which indicate sorting columns.</p>
</li>
<li><p>For each sorting column:</p>
<ul>
<li><p>It gets the column index and finds the corresponding column name.</p>
</li>
<li><p>It determines the sort direction (ascending or descending).</p>
</li>
<li><p>If descending, it prepends a minus sign to the column name.</p>
</li>
<li><p>It adds this to the <code>order_by_list</code>.</p>
</li>
</ul>
</li>
<li><p>If no sorting is specified, it defaults to the first column in <code>post_columns</code>.</p>
</li>
</ul>
<p>This function effectively translates DataTables' sorting parameters into Django ORM ordering syntax, allowing for dynamic, multi-column sorting as specified by the user in the frontend.</p>
<ol start="4">
<li>Template Changes: In the HTML template, I set up the table structure:</li>
</ol>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"blog-posts-table"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table table-striped table-hover table-sm table-responsive"</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>
      {% if blog.show_author_id %}
        <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>ID<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      {% endif %}
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>AUTHOR<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</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>&gt;</span>CATEGORY<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>PUBLISHED DATE<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>VIEWS<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>COMMENTS<span class="hljs-tag">&lt;/<span class="hljs-name">th</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">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"material-symbols-outlined md-18"</span>&gt;</span>edit<span class="hljs-tag">&lt;/<span class="hljs-name">span</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>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Toggle Comments"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"material-symbols-outlined"</span>&gt;</span>comments<span class="hljs-tag">&lt;/<span class="hljs-name">span</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">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><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>
</code></pre>
<ol start="5">
<li>URL Configuration: I added a new URL pattern for the API view:</li>
</ol>
<pre><code class="lang-python">path(
    <span class="hljs-string">"api/blog-posts-data"</span>,
    views.blog_posts_datatables_api_view,
    name=<span class="hljs-string">"blog-posts-datatables-data"</span>,
),
</code></pre>
<p>The Result: The end product was a highly efficient and flexible blog post management table. It loads quickly, even with thousands of posts, and provides smooth sorting, searching, and pagination - all handled server-side.</p>
<p>Key Takeaways:</p>
<ol>
<li><p>Copying and modifying the request data allows for flexible response handling.</p>
</li>
<li><p>Server-side processing is crucial for large datasets.</p>
</li>
<li><p>Dynamic column handling requires careful planning but offers great flexibility.</p>
</li>
<li><p>Efficient database querying (using select_related and filter) is key to maintaining performance.</p>
</li>
<li><p>Breaking down complex logic into helper functions improves readability and maintainability.</p>
</li>
<li><p>Handling HTML generation server-side for action buttons allows for dynamic permissions and URLs.</p>
</li>
</ol>
<p>This solution has been a game-changer for my blog management project. It's scalable, efficient, and provides a great user experience while handling complex data structures and permissions.</p>
<p>Have you tackled similar challenges with large, dynamic datasets in Django? I'd love to hear your approaches and any tips you might have!</p>
<p>p.s - i am still learning and chances are there may be better approach than this to implement server side datatables. i just shared my way of doing this but if i found a new better way then i will mention that here.</p>
]]></content:encoded></item><item><title><![CDATA[Supercharge Your Django Logging: Custom Filters for the Win]]></title><description><![CDATA[The Problem
Default Django logs are okay, but they lack juicy details like IP addresses, browsers, and user info. Wouldn't it be cool to have all that at your fingertips?
Enter Custom Logging Filters
Django lets us create custom filters to add extra ...]]></description><link>https://blog.sorv.dev/supercharge-your-django-logging-custom-filters-for-the-win</link><guid isPermaLink="true">https://blog.sorv.dev/supercharge-your-django-logging-custom-filters-for-the-win</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[django orm]]></category><category><![CDATA[logging]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Tue, 24 Sep 2024 07:33:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Fi-GJaLRGKc/upload/ddfc01dd2917810ec157a30a537806d8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem">The Problem</h2>
<p>Default Django logs are okay, but they lack juicy details like IP addresses, browsers, and user info. Wouldn't it be cool to have all that at your fingertips?</p>
<h2 id="heading-enter-custom-logging-filters">Enter Custom Logging Filters</h2>
<p>Django lets us create custom filters to add extra info to our logs. Here's how:</p>
<ol>
<li>First, set up your logging config in <a target="_blank" href="http://settings.py">settings.py</a>. It'll look something like this:</li>
</ol>
<pre><code class="lang-python">LOGGING = {
    <span class="hljs-comment"># ... other config ...</span>
    <span class="hljs-string">"filters"</span>: {
        <span class="hljs-string">"custom_request_filter"</span>: {
            <span class="hljs-string">"()"</span>: <span class="hljs-string">"path.to.your.CustomRequestFilter"</span>,
        },
    },
    <span class="hljs-comment"># ... more config ...</span>
}
</code></pre>
<ol start="2">
<li>Now, let's create our custom filter:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> django.core.exceptions <span class="hljs-keyword">import</span> ObjectDoesNotExist


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomRequestFilter</span>(<span class="hljs-params">logging.Filter</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter</span>(<span class="hljs-params">self, record</span>):</span>
        <span class="hljs-keyword">if</span> hasattr(record, <span class="hljs-string">"request"</span>):
            request = record.request  <span class="hljs-comment"># type: ignore</span>
            <span class="hljs-keyword">try</span>:
                record.ip_address = request.META.get(<span class="hljs-string">"REMOTE_ADDR"</span>, <span class="hljs-string">"-"</span>)
                record.device_type = request.META.get(<span class="hljs-string">"HTTP_SEC_CH_UA_PLATFORM"</span>)
                <span class="hljs-comment"># ex string - "Brave";v="129", "Not=A?Brand";v="8", "Chromium";v="129"</span>
                record.browser = request.META.get(<span class="hljs-string">"HTTP_SEC_CH_UA"</span>).split(<span class="hljs-string">";"</span>)[<span class="hljs-number">0</span>]
            <span class="hljs-keyword">except</span> AttributeError:
                record.browser = <span class="hljs-string">"-"</span>
                record.device_type = <span class="hljs-string">"-"</span>
                record.ip_address = <span class="hljs-string">"-"</span>
            <span class="hljs-keyword">try</span>:
                record.user = request.user.username <span class="hljs-keyword">if</span> request.user.is_authenticated <span class="hljs-keyword">else</span> <span class="hljs-string">"unauthenticated"</span>
            <span class="hljs-keyword">except</span> (ObjectDoesNotExist, AttributeError):
                record.user = <span class="hljs-string">"unauthenticated"</span>
            record.http_status = getattr(record, <span class="hljs-string">"status_code"</span>, <span class="hljs-string">"-"</span>)
        <span class="hljs-keyword">else</span>:
            record.ip_address = <span class="hljs-string">"-"</span>
            record.user = <span class="hljs-string">"-"</span>
            record.device_type = <span class="hljs-string">"-"</span>
            record.browser = <span class="hljs-string">"-"</span>
            record.http_status = <span class="hljs-string">"-"</span>
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
</code></pre>
<ol start="3">
<li>Apply the filter to your handlers:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-string">"handlers"</span>: {
    <span class="hljs-string">"file"</span>: {
        <span class="hljs-comment"># ... other config ...</span>
        <span class="hljs-string">"filters"</span>: [<span class="hljs-string">"custom_request_filter"</span>],
    },
},
</code></pre>
<ol start="4">
<li>Update your formatter to use the new attributes:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-string">"formatters"</span>: {
    <span class="hljs-string">"verbose"</span>: {
        <span class="hljs-string">"format"</span>: <span class="hljs-string">"{asctime} {ip_address} {user} {message}"</span>,
        <span class="hljs-string">"style"</span>: <span class="hljs-string">"{"</span>,
    },
},
</code></pre>
<h2 id="heading-bonus-logging-in-websocket-consumers">Bonus: Logging in WebSocket Consumers</h2>
<p>Don't forget about your WebSocket consumers! They need love too. Here's a quick tip:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FocusSessionConsumer</span>(<span class="hljs-params">AsyncWebsocketConsumer</span>):</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connect</span>(<span class="hljs-params">self</span>):</span>
        self.request = self._generate_request_metadata()
        <span class="hljs-comment"># ... other connect logic ...</span>
        logger.info(<span class="hljs-string">f"<span class="hljs-subst">{self.user.username}</span> connected"</span>, extra={<span class="hljs-string">"request"</span>: self.request})

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_generate_request_metadata</span>(<span class="hljs-params">self</span>):</span>
        request = HttpRequest()
        <span class="hljs-comment"># Populate request.META with relevant info</span>
        <span class="hljs-keyword">return</span> request
</code></pre>
<h2 id="heading-here-are-some-code-snippets-from-how-i-use-it-in-django-channels-context">Here are some code snippets from how i use it in django channels context</h2>
<h3 id="heading-consumerpy"><code>consumer.py</code></h3>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FocusSessionConsumer</span>(<span class="hljs-params">AsyncWebsocketConsumer</span>):</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connect</span>(<span class="hljs-params">self</span>):</span>
        self.user = self.scope[<span class="hljs-string">"user"</span>]
        self.username = self.scope[<span class="hljs-string">"url_route"</span>][<span class="hljs-string">"kwargs"</span>][<span class="hljs-string">"username"</span>]
        self.request = self._generate_request_metadata()
        <span class="hljs-keyword">await</span> self.channel_layer.group_add(self.session_group_name, self.channel_name)  <span class="hljs-comment"># type: ignore</span>
        <span class="hljs-keyword">await</span> self.accept()
        <span class="hljs-comment"># othe code related to project</span>
        logger.info(<span class="hljs-string">f"<span class="hljs-subst">{self.user.username}</span> connected to session '<span class="hljs-subst">{self.session_id}</span>'"</span>, extra={<span class="hljs-string">"request"</span>: self.request})
        <span class="hljs-keyword">await</span> self.update_session_followers_list_to_all_clients()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_generate_request_metadata</span>(<span class="hljs-params">self</span>):</span>
        request = HttpRequest()
        user_agent = str(self.scope[<span class="hljs-string">"headers"</span>][<span class="hljs-number">4</span>][<span class="hljs-number">1</span>].strip())
        <span class="hljs-keyword">try</span>:
            ip_add = self.scope[<span class="hljs-string">"client"</span>][<span class="hljs-number">0</span>]
        <span class="hljs-keyword">except</span> Exception:
            ip_add = <span class="hljs-string">"-"</span>
        <span class="hljs-keyword">try</span>:
            user_os = user_agent.rsplit(<span class="hljs-string">"("</span>)[<span class="hljs-number">1</span>].split(<span class="hljs-string">";"</span>)[<span class="hljs-number">0</span>]
        <span class="hljs-keyword">except</span> Exception:
            user_os = <span class="hljs-string">"-"</span>
        request.META = {
            <span class="hljs-string">"REMOTE_ADDR"</span>: ip_add,
            <span class="hljs-string">"HTTP_SEC_CH_UA_PLATFORM"</span>: user_os,
        }
        request.user = self.user
        <span class="hljs-keyword">return</span> request
</code></pre>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>With these custom filters, your logs will be bursting with useful info. Happy debugging, folks!</p>
<p>Remember, logging is like a good curry - it should be rich, flavorful, and help you find the source of the problem. Now go forth and log like a pro! 🚀📝</p>
<p>p.s - i am still learning so there could be some mistakes in this blog. please test your code after implementing.</p>
]]></content:encoded></item><item><title><![CDATA[Unveiling Django's Powerful Annotate Function]]></title><description><![CDATA[Django's ORM is a powerful tool that can make interacting with your database as simple as working with Python code. One of the most powerful parts of Django's ORM is the annotate function, which can add extra "fields" to your models, based on complex...]]></description><link>https://blog.sorv.dev/unveiling-djangos-powerful-annotate-function</link><guid isPermaLink="true">https://blog.sorv.dev/unveiling-djangos-powerful-annotate-function</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[django orm]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[SQLite]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Mon, 24 Jul 2023 10:59:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cckf4TsHAuw/upload/14e45481fccc6a5e6e9287719240651a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Django's ORM</strong> is a powerful tool that can make interacting with your database as simple as working with Python code. One of the most powerful parts of Django's ORM is the <code>annotate</code> function, which can add extra "fields" to your models, based on complex aggregations and computations. This can be incredibly helpful when you want to aggregate data from related models, or perform complex, database-level calculations.</p>
<p>This blog post will take you through the basics of Django's <code>annotate</code> function and then delve into some more advanced uses, helping you to fully leverage the power of Django's ORM in your projects.</p>
<h3 id="heading-the-basics"><strong>The Basics</strong></h3>
<p>At its core, <code>annotate()</code> is about adding new, calculated fields to the objects in a queryset. Let's start with a simple example. Let's say we're working with two models - <code>Blog</code> and <code>Comment</code>:</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">Blog</span>(<span class="hljs-params">models.Model</span>):</span>
    title = models.CharField(max_length=<span class="hljs-number">200</span>)
    content = models.TextField()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Comment</span>(<span class="hljs-params">models.Model</span>):</span>
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name=<span class="hljs-string">'comments'</span>)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=<span class="hljs-literal">True</span>)
</code></pre>
<p>Now, if you want to find out how many comments each blog post has, you could use <code>annotate()</code> with the <code>Count</code> aggregation, like so:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Count

<span class="hljs-comment"># Get a queryset of all blogs</span>
blogs = Blog.objects.all()

<span class="hljs-comment"># Annotate each blog with the count of its related comments</span>
blogs_with_counts = blogs.annotate(comment_count=Count(<span class="hljs-string">'comments'</span>))

<span class="hljs-keyword">for</span> blog <span class="hljs-keyword">in</span> blogs_with_counts:
    print(<span class="hljs-string">f"The blog '<span class="hljs-subst">{blog.title}</span>' has <span class="hljs-subst">{blog.comment_count}</span> comments."</span>)
</code></pre>
<p>Here, <code>Count</code> is an aggregation function that counts the number of related objects. We're using it to count the number of related <code>Comment</code> objects and adding that count to each <code>Blog</code> object in the queryset under the field <code>comment_count</code>.</p>
<h3 id="heading-beyond-basics-dive-into-advanced-usage"><strong>Beyond Basics: Dive into Advanced Usage</strong></h3>
<p>Django's <code>annotate()</code> function can do much more than just simple aggregations. You can use it to perform more complex queries and computations.</p>
<h4 id="heading-conditional-expressions">Conditional Expressions:</h4>
<p>Django provides classes to represent SQL’s conditional expressions. Here’s an example using <code>Case</code> and <code>When</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Case, When, Count, IntegerField

blogs = Blog.objects.annotate(
    comment_count=Count(<span class="hljs-string">'comments'</span>)
).annotate(
    has_many_comments=Case(
        When(comment_count__gt=<span class="hljs-number">10</span>, then=<span class="hljs-number">1</span>),
        default=<span class="hljs-number">0</span>,
        output_field=IntegerField(),
    ),
)
</code></pre>
<p>This will annotate a QuerySet of blogs with a 'has_many_comments' field. The field is 1 if the blog has more than 10 comments, and 0 otherwise.</p>
<h4 id="heading-expressions-with-f-objects">Expressions with F objects:</h4>
<p>F expressions can be used in annotate to perform database operations without pulling the data into python.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db.models.functions <span class="hljs-keyword">import</span> Length

<span class="hljs-comment"># This will annotate the QuerySet with the length of the title.</span>
blogs = Blog.objects.annotate(title_length=Length(<span class="hljs-string">'title'</span>))
</code></pre>
<h4 id="heading-using-multiple-aggregations">Using multiple aggregations:</h4>
<p>You can use more than one aggregation function in your annotate clause.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Count, Avg

<span class="hljs-comment"># This will annotate the QuerySet with both the count of comments and the average id of comments.</span>
blogs = Blog.objects.annotate(
    comment_count=Count(<span class="hljs-string">'comments'</span>),
    average_comment_id=Avg(<span class="hljs-string">'comments__id'</span>)
)
</code></pre>
<h4 id="heading-annotations-on-related-models">Annotations on related models:</h4>
<p>You can annotate related models to perform calculations that need to access related fields.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Sum

<span class="hljs-comment"># This will annotate each comment of each blog with the total views of the blog it belongs to.</span>
comments = Comment.objects.select_related(<span class="hljs-string">'blog'</span>).annotate(
    blog_views=Sum(<span class="hljs-string">'blog__views'</span>)
)
</code></pre>
<h4 id="heading-complex-annotations-with-custom-aggregates">Complex annotations with custom aggregates:</h4>
<p>Django also allows you to define your own aggregate functions. This is more advanced and requires an understanding of how your database works.</p>
<p>These are just examples, but Django's ORM is incredibly powerful and flexible. You can create some very complex queries once you get a handle on how it all works.</p>
<p>In summary, Django's <code>annotate</code> is a powerful feature, allowing you to perform complex database queries and aggregations easily. It's a tool that can be a real game-changer once you learn how to use it effectively. Always remember to check Django's documentation, which is very comprehensive and includes many examples and explanations. Happy Coding!</p>
]]></content:encoded></item><item><title><![CDATA[Get Access to Your Remote PostgreSQL Database running on VPS in No Time: Here's How]]></title><description><![CDATA[Connecting to a PostgreSQL database running on an Ubuntu VPS from a local machine using VS Code involves the following steps:

Install the "PostgreSQL" extension in VS Code:

it should look like the below screenshot.👇
  
  Click "Install" to install...]]></description><link>https://blog.sorv.dev/get-access-to-your-remote-postgresql-database-running-on-vps-in-no-time-heres-how</link><guid isPermaLink="true">https://blog.sorv.dev/get-access-to-your-remote-postgresql-database-running-on-vps-in-no-time-heres-how</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[vps]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Wed, 28 Dec 2022 03:11:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672197027277/df6cee5e-babb-4afc-bca7-39a6b85e2ab8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Connecting to a PostgreSQL database running on an Ubuntu VPS from a local machine using VS Code involves the following steps:</p>
<ol>
<li><p>Install the "PostgreSQL" extension in VS Code:</p>
<ul>
<li><p>it should look like the below screenshot.👇</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672194168825/026e9f0a-cf4d-4274-b3d9-fa36ebd6d1fa.png" alt class="image--center mx-auto" /></p>
<p>  Click "Install" to install the extension</p>
</li>
</ul>
</li>
<li><p>Configure the remote PostgreSQL server to accept connections from your local machine. To do this, you will need to edit the <code>pg_hba.conf</code> file, which controls which clients are allowed to connect to the server. Follow these steps:</p>
<ul>
<li><p>Connect to your Ubuntu VPS using SSH</p>
</li>
<li><p>Navigate to the <code>pg_hba.conf</code> file, which is usually located at <code>/etc/postgresql/10/main/pg_hba.conf</code> (<em>the exact path may vary depending on your PostgreSQL version</em>)</p>
</li>
<li><p>Edit the file using a text editor such as vi or nano:</p>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-bash">    sudo vi pg_hba.conf
</code></pre>
<ul>
<li><p>under <code>IPv4 local connections</code>, set it to your ipaddress or <code>0.0.0.0/0</code> if your IP is dynamic.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672194472641/b31e0ba7-b4fe-42d8-ad95-d2d3e82a4eae.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ol>
<li><p>Configure the remote PostgreSQL server to listen for connections on the network. By default, PostgreSQL only listens for connections on the local loopback interface (127.0.0.1). To allow connections from other hosts, you will need to edit the <code>postgresql.conf</code> file.</p>
<ul>
<li><p>Navigate to the <code>postgresql.conf</code> file, which is usually located at <code>/etc/postgresql/10/main/postgresql.conf</code> (<em>the exact path may vary depending on your PostgreSQL version</em>)</p>
</li>
<li><p>Edit the file using a text editor such as vi or nano:</p>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-bash">    sudo vi postgresql.conf
</code></pre>
<ul>
<li><p>Find the line that reads <code>listen_addresses = '</code><a target="_blank" href="http://localhost"><code>localhost</code></a><code>'</code> and change it to <code>listen_addresses = 'your_server_ipaddress'</code> like below.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672195070894/91aa8a66-785f-42a0-b8ea-5a0bfae4cbb8.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ol>
<li><p>Restart the PostgreSQL server to apply the changes. You can do this by running the following command:</p>
<pre><code class="lang-bash"> sudo systemctl restart postgresql
</code></pre>
</li>
<li><p>Connect to the remote PostgreSQL server from VS Code. To do this, follow these steps:</p>
<ul>
<li><p>Click on the Database icon in left of sidebar. it should like like below image.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672195360480/43bb3cba-6cee-48af-9a9d-2d2fe4c664c6.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Click on create new connection and you will see the screen like below image.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672195451084/569bb529-6891-45b1-b3af-9b5f0c9430fc.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>fill all the parameters including ones in ssh tunnel too.</p>
</li>
<li><p>click connect after filling everything.</p>
</li>
<li><p>now you will see all the database tables in sidebar.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672195612889/c8e17909-1bd6-489f-8a72-9b848d428d06.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
<h1 id="heading-is-this-method-secure">Is this method secure 🤔</h1>
<p>The method described above for connecting to a PostgreSQL database running on an Ubuntu VPS from a local machine using vs code is generally secure, as it uses the md5 authentication method and requires a valid username and password to access the database.</p>
<p>However, still there can be lot of things to make it secure &amp; I don't have much info on how to make it most secure. but there are a few additional steps you can take to further secure the connection:</p>
<ul>
<li><p>Use a strong password for the PostgreSQL user. Make sure to use a password that is difficult to guess and not shared with any other accounts.</p>
</li>
<li><p>Enable SSL encryption for the connection. To do this, you will need to edit the <code>postgresql.conf</code> file on the remote server and set the <code>ssl</code> parameter to <code>on</code>. You will also need to generate SSL certificates and configure them on both the client and server sides. Checkout this <a target="_blank" href="https://www.howtoforge.com/how-to-enable-ssl-for-postgresql-connections/">tutorial</a> for more info on this step.</p>
</li>
<li><p>Use a firewall to limit access to the PostgreSQL server. You can use a firewall such as iptables or ufw to allow connections only from trusted IP addresses or networks.</p>
</li>
<li><p>Regularly update and patch the PostgreSQL server to keep it secure. Make sure to keep the server up to date with the latest security updates and patches.</p>
</li>
</ul>
<p>you can do more research on making it more secure.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672196321602/ffd94bf1-82a9-41ad-a4f9-846b03653de7.gif" alt class="image--center mx-auto" /></p>
<p>In <strong>conclusion</strong>, connecting to a PostgreSQL database from a local machine using a GUI tool such as pgAdmin or a VS Code extension like <strong>PostgreSQL</strong> by weijan can be a convenient way to manage and query the database. To do this, you will need to install the appropriate tool, configure the remote PostgreSQL server to accept connections, and establish a connection to the database using the hostname, port, username, and password. By following the steps outlined in this answer, you should be able to successfully connect to a PostgreSQL database from a local machine.</p>
<p>It is important to keep in mind that security is an important aspect of any database connection. To ensure that your connection is secure, it is recommended to use a strong password, enable SSL encryption, use a firewall to limit access to the server, and regularly update and patch the PostgreSQL server.</p>
<p>I hope this tutorial helps you. if you have any questions or suggestions, pls drop them in the comments.💬</p>
<h3 id="heading-if-you-want-a-django-developer-who-can-bring-your-project-to-life-dont-hesitate-to-contact-me-and-lets-discuss-your-needs">If you want a Django developer who can bring your project to life, don't hesitate to contact me and let's discuss your needs! 😀</h3>
]]></content:encoded></item><item><title><![CDATA[Effortlessly improve the quality and consistency of your code with these pre-commit Hooks!]]></title><description><![CDATA[Using pre-commit tools can be a great way to improve the quality and consistency of your code, as well as streamline your development workflow. Pre-commit tools are tools that run automatically before you commit your code changes to a version control...]]></description><link>https://blog.sorv.dev/effortlessly-improve-the-quality-and-consistency-of-your-code-with-these-pre-commit-hooks</link><guid isPermaLink="true">https://blog.sorv.dev/effortlessly-improve-the-quality-and-consistency-of-your-code-with-these-pre-commit-hooks</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Python]]></category><category><![CDATA[clean code]]></category><category><![CDATA[Clean Architecture]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Mon, 26 Dec 2022 13:06:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672059915655/c6e449c1-46d5-4b5e-8d15-7de1c6c1c4bf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Using pre-commit tools can be a great way to improve the quality and consistency of your code, as well as streamline your development workflow. Pre-commit tools are tools that run automatically before you commit your code changes to a version control system like Git. They can check your code for syntax errors, style issues, and other problems, and can even automatically fix some of these issues for you.</p>
<p>To use pre-commit tools, you will first need to install the pre-commit framework, which is a tool that allows you to manage and automate pre-commit hooks. You can install the pre-commit framework using pip:</p>
<pre><code class="lang-bash">pip install pre-commit
</code></pre>
<p>Once the pre-commit framework is installed, you can configure it to use specific pre-commit tools by creating a <code>.pre-commit-config.yaml</code> file in the root directory of your project. This file should specify the pre-commit hooks that you want to use, as well as any arguments or configuration options that you want to pass to these hooks.</p>
<p>For example, here is a <code>.pre-commit-config.yaml</code> file that configures the pre-commit framework to use the <code>black</code> and <code>flake8</code> pre-commit tools:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">exclude:</span> <span class="hljs-string">"^docs/|/migrations/"</span>
<span class="hljs-attr">default_stages:</span> [<span class="hljs-string">commit</span>]

<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.4.0</span>
    <span class="hljs-attr">hooks:</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">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">check-yaml</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.3.1</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">--py310-plus</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.12</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.11</span><span class="hljs-number">.4</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/PyCQA/flake8</span>
    <span class="hljs-attr">rev:</span> <span class="hljs-number">6.0</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">flake8</span>
        <span class="hljs-attr">args:</span> [<span class="hljs-string">"--config=setup.cfg"</span>]
        <span class="hljs-attr">additional_dependencies:</span> [<span class="hljs-string">flake8-isort</span>]
</code></pre>
<p>In this example, the <code>black</code> tool is used to automatically format Python code using the Black code style, while the <code>flake8</code> tool is used to check for syntax errors and style.</p>
<p>To use the pre-commit tools configured in your <code>.pre-commit-config.yaml</code> file, you will need to install the pre-commit framework's git hook. You can do this by running the following command from the root directory of your project:</p>
<pre><code class="lang-bash">pre-commit install
</code></pre>
<p>This will install a git hook that will run the configured pre-commit tools automatically before you commit any changes to your code.</p>
<p>You can also run the pre-commit tools manually at any time by using the <code>pre-commit run</code> command. This can be useful if you want to check your code for problems without actually committing it, or if you want to run the pre-commit tools on specific files or directories.</p>
<p>By using pre-commit tools, you can automate many of the tedious and error-prone tasks involved in code review and quality assurance, and can focus on the more important aspects of your project. Pre-commit tools can also help you enforce consistent coding standards and practices across your team, and can make it easier to maintain and update your code over time.</p>
<p>To know more about pre-commits, check this <a target="_blank" href="https://pre-commit.com/">pre commit docs</a></p>
<p>If you want a Django developer who can bring your project to life, don't hesitate to contact me and let's discuss your needs! 😀</p>
]]></content:encoded></item><item><title><![CDATA[Never Worry About Inconsistent Coding Styles Again: Introducing .editorconfig ✨]]></title><description><![CDATA[The .editorconfig file is a simple configuration file that helps developers define and maintain consistent coding styles between different editors and IDEs. It is a plain text file that can be placed in the root directory of a project, and specifies ...]]></description><link>https://blog.sorv.dev/never-worry-about-inconsistent-coding-styles-again-introducing-editorconfig</link><guid isPermaLink="true">https://blog.sorv.dev/never-worry-about-inconsistent-coding-styles-again-introducing-editorconfig</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[code format]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Mon, 26 Dec 2022 12:27:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672056795233/19995665-4584-427a-bc96-f188dd3d4f9e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <code>.editorconfig</code> file is a simple configuration file that helps developers define and maintain consistent coding styles between different editors and IDEs. It is a plain text file that can be placed in the root directory of a project, and specifies settings such as indentation style, character encoding, and line ending style.</p>
<p>The <code>.editorconfig</code> file uses a simple INI-style format, with sections for each file pattern and properties for each setting. For example, a <code>.editorconfig</code> file might look something like this:</p>
<pre><code class="lang-ini"><span class="hljs-comment"># Top-most EditorConfig file</span>
<span class="hljs-attr">root</span> = <span class="hljs-literal">true</span>

<span class="hljs-comment"># All files</span>
<span class="hljs-section">[*]</span>
<span class="hljs-attr">indent_style</span> = space
<span class="hljs-attr">indent_size</span> = <span class="hljs-number">2</span>
<span class="hljs-attr">end_of_line</span> = lf
<span class="hljs-attr">charset</span> = utf-<span class="hljs-number">8</span>
<span class="hljs-attr">trim_trailing_whitespace</span> = <span class="hljs-literal">true</span>
<span class="hljs-attr">insert_final_newline</span> = <span class="hljs-literal">true</span>

<span class="hljs-comment"># Python files</span>
<span class="hljs-section">[*.py]</span>
<span class="hljs-attr">indent_size</span> = <span class="hljs-number">4</span>
</code></pre>
<p>This <code>.editorconfig</code> file specifies that all files should use 2-space indentation, UTF-8 character encoding, and Unix-style line endings, and that trailing whitespace should be trimmed and a final newline should be inserted. It also specifies that Python files should use 4-space indentation.</p>
<p>To use the <code>.editorconfig</code> file with a particular editor or IDE, you will typically need to install a plugin or extension that adds support for EditorConfig. Once the plugin is installed, the editor or IDE will automatically read the <code>.editorconfig</code> file in the root directory of the project (or any of its parent directories) and apply the specified settings. This can help ensure that coding styles are consistent across different development environments and editors, even if different developers are using different editors.</p>
<p>EditorConfig is supported by a wide range of editors and IDEs, including Sublime Text, Visual Studio Code, and many others. By using a <code>.editorconfig</code> file, you can help ensure that your project's coding style is consistent and easy to maintain, regardless of the editor or IDE being used.</p>
<h1 id="heading-summary">Summary 🎯</h1>
<p>To summarize, the <code>.editorconfig</code> file is an essential tool for anyone looking to ensure consistent coding styles in their projects. With support from a wide range of editors and IDEs, <code>.editorconfig</code> is an easy and effective way to maintain a consistent coding style across different development environments. So if you want to improve the quality and consistency of your code, give <code>.editorconfig</code> a try today!</p>
<p>If you want a Django developer who can bring your project to life, don't hesitate to contact me and let's discuss your needs! 😀</p>
]]></content:encoded></item><item><title><![CDATA[Boost your Django skills: Learn how to use super()]]></title><description><![CDATA[The super() keyword is a useful tool in Django, a high-level Python web framework, for working with inheritance in classes. It allows a subclass to refer to its parent class, allowing the subclass to inherit attributes and methods from the parent cla...]]></description><link>https://blog.sorv.dev/boost-your-django-skills-learn-how-to-use-super</link><guid isPermaLink="true">https://blog.sorv.dev/boost-your-django-skills-learn-how-to-use-super</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[django forms]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sat, 17 Dec 2022 16:11:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671293268493/TluYVsnJo.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <code>super()</code> keyword is a useful tool in Django, a high-level Python web framework, for working with inheritance in classes. It allows a subclass to refer to its parent class, allowing the subclass to inherit attributes and methods from the parent class while also being able to override or extend them as needed.</p>
<p>Using <code>super()</code> can greatly simplify the process of working with inheritance in Django, allowing you to easily reuse code and customize your models, views, forms, and other components to fit your specific needs. In this article, we'll take a closer look at how to use the <code>super()</code> keyword in Django and explore some common use cases.</p>
<h3 id="heading-inheriting-attributes-and-methods-in-the-init-method"><strong>Inheriting attributes and methods in the</strong> <code>__init__</code> method</h3>
<p>One common use of <code>super()</code> in Django is in the <code>__init__</code> method of a subclass. This method is called when an instance of the subclass is created, and it is used to initialize the attributes of the instance.</p>
<p>For example, consider a Django model called <code>Product</code> with a subclass called <code>Book</code>. The <code>Product</code> model might have fields for a title and a price, while the <code>Book</code> model might have an additional field for the author. In the <code>__init__</code> method of the <code>Book</code> model, we can use <code>super()</code> to call the <code>__init__</code> method of the <code>Product</code> model and pass in the title and price fields, while also adding the <code>author</code> field specific to the <code>Book</code> model:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>
    title = models.CharField(max_length=<span class="hljs-number">200</span>)
    price = models.DecimalField(decimal_places=<span class="hljs-number">2</span>, max_digits=<span class="hljs-number">10</span>)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span>(<span class="hljs-params">Product</span>):</span>
    author = models.CharField(max_length=<span class="hljs-number">200</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
        <span class="hljs-comment"># additional procesing before saving</span>
        super().__init__(*args, **kwargs)
</code></pre>
<p>Using <code>super().__init__(*args, **kwargs)</code> in this way allows the <code>Book</code> model to inherit the <code>title</code> and <code>price</code> fields from the <code>Product</code> model, while also adding the <code>author</code> field specific to the <code>Book</code> model.</p>
<h3 id="heading-overriding-or-extending-methods-in-the-subclass"><strong>Overriding or extending methods in the subclass</strong></h3>
<p>Another common use of <code>super()</code> in Django is in the <code>save</code> method of a model. This method is called when the model instance is saved to the database, and it is used to persist any updates to the model instance.</p>
<p>For example, consider a Django model called <code>Product</code> with a <code>save</code> method that updates a <code>last_modified</code> field every time the model is saved. A subclass of <code>Product</code> called <code>Book</code> might want to override the <code>save</code> method to do some additional processing before saving the model. In this case, the <code>save</code> method of the <code>Book</code> model could use <code>super()</code> to call the <code>save</code> method of the <code>Product</code> model and pass in any additional arguments, while also doing the additional</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>
    title = models.CharField(max_length=<span class="hljs-number">200</span>)
    price = models.DecimalField(decimal_places=<span class="hljs-number">2</span>, max_digits=<span class="hljs-number">10</span>)
    last_modified = models.DateTimeField(auto_now=<span class="hljs-literal">True</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
        self.last_modified = timezone.now()
        self.save()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span>(<span class="hljs-params">Product</span>):</span>
    author = models.CharField(max_length=<span class="hljs-number">200</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
        <span class="hljs-comment"># Do additional processing here</span>
        super().save(*args, **kwargs)
</code></pre>
<p>In this revised version of the example code, the <code>save</code> method of the <code>Product</code> model saves the model instance after updating the <code>last_modified</code> field. When the <code>save</code> method of the <code>Book</code> model is called, it uses <code>super().save(*args, **kwargs)</code> to call the <code>save</code> method of the <code>Product</code> model, which saves the model instance with the updated <code>last_modified</code> field.</p>
<h3 id="heading-using-super-in-a-django-serializer"><strong>Using</strong> <code>super()</code> in a Django serializer</h3>
<p>In Django, serializers are used to convert Django models or querysets into JSON format, allowing you to easily send data over the web or store it in a database. Serializers are typically used in combination with Django views and Django Rest Framework, a powerful toolkit for building APIs in Django.</p>
<p>You can use <code>super()</code> in a Django serializer to easily inherit attributes and methods from a parent serializer and customize or extend them as needed. For example, consider a Django serializer called <code>ProductSerializer</code> with a subclass called <code>BookSerializer</code>. The <code>ProductSerializer</code> might have fields for a <code>title</code> and a <code>price</code>, while the <code>BookSerializer</code> might have an additional field for the <code>author</code>. In the <code>BookSerializer</code>, we can use <code>super()</code> to call the <code>ProductSerializer</code> and pass in the <code>title</code> and <code>price</code> fields, while also adding the <code>author</code> field specific to the <code>BookSerializer.</code></p>
<h3 id="heading-using-super-in-django-template-views"><strong>Using</strong> <code>super()</code> in Django template views</h3>
<p>Django template views are used to render HTML templates and pass data to them for display. You can use <code>super()</code> in a Django template view to easily inherit attributes and methods from a parent template view and customize or extend them as needed.</p>
<p>One common use of <code>super()</code> in Django template views is in the <code>get_context_data</code> method, which is used to pass data to the template for rendering. For example, consider a Django template view called <code>ProductView</code> with a subclass called <code>BookView</code>. The <code>ProductView</code> might have a <code>title</code> variable, while the <code>BookView</code> might want to add an additional <code>author</code> variable. In the <code>BookView</code>, we can use <code>super()</code> to call the <code>get_context_data</code> method of the <code>ProductView</code> and pass in the <code>title</code> variable, while also adding the <code>author</code> variable specific to the <code>BookView</code>:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductView</span>(<span class="hljs-params">TemplateView</span>):</span>
    template_name = <span class="hljs-string">'product.html'</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</span>):</span>
        context = super().get_context_data(**kwargs)
        context[<span class="hljs-string">'title'</span>] = <span class="hljs-string">'Product'</span>
        <span class="hljs-keyword">return</span> context

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookView</span>(<span class="hljs-params">ProductView</span>):</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</span>):</span>
        context = super().get_context_data(**kwargs)
        context[<span class="hljs-string">'author'</span>] = <span class="hljs-string">'John Doe'</span>
        <span class="hljs-keyword">return</span> context
</code></pre>
<p>Using <code>super().get_context_data(**kwargs)</code> in this way allows the <code>BookView</code> to inherit the <code>title</code> variable from the <code>ProductView.</code></p>
<h3 id="heading-using-super-in-django-forms"><strong>Using</strong> <code>super()</code> in Django forms</h3>
<p>Django forms are used to validate and process user input. You can use <code>super()</code> in a Django form to easily inherit attributes and methods from a parent form and customize or extend them as needed.</p>
<p>One common use of <code>super()</code> in Django forms is in the <code>validate</code> method, which is used to perform validation on the form data. For example, consider a Django form called <code>ProductForm</code> with a subclass called <code>BookForm</code>. The <code>ProductForm</code> might have a field for a <code>title</code>, while the <code>BookForm</code> might want to add an additional <code>author</code> field and validate that the <code>author</code> field is not empty. In the <code>BookForm</code>, we can use <code>super()</code> to call the <code>validate</code> method of the <code>ProductForm</code> and pass in the <code>cleaned_data</code> dictionary, while also performing the additional validation for the <code>BookForm</code>:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductForm</span>(<span class="hljs-params">forms.Form</span>):</span>
    title = forms.CharField(max_length=<span class="hljs-number">200</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">validate</span>(<span class="hljs-params">self</span>):</span>
        cleaned_data = super().validate()
        title = cleaned_data.get(<span class="hljs-string">'title'</span>)
        <span class="hljs-comment"># Validate the title field</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> title:
            <span class="hljs-keyword">raise</span> forms.ValidationError(<span class="hljs-string">'Please enter a title.'</span>)
        <span class="hljs-keyword">return</span> cleaned_data

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookForm</span>(<span class="hljs-params">ProductForm</span>):</span>
    author = forms.CharField(max_length=<span class="hljs-number">200</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">validate</span>(<span class="hljs-params">self</span>):</span>
        cleaned_data = super().validate()
        author = cleaned_data.get(<span class="hljs-string">'author'</span>)
        <span class="hljs-comment"># Validate the author field</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> author:
            <span class="hljs-keyword">raise</span> forms.ValidationError(<span class="hljs-string">'Please enter an author.'</span>)
        <span class="hljs-keyword">return</span> cleaned_data
</code></pre>
<p>Using <code>super().validate()</code> in this way allows the <code>BookForm</code> to inherit the validation for the <code>title</code> field from the <code>ProductForm</code>, while also performing additional validation for the <code>author</code> field specific to the <code>BookForm</code>.</p>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>The <code>super()</code> keyword is a useful tool in Django for working with inheritance in classes. It allows a subclass to easily inherit attributes and methods from a parent class and customize or extend them as needed. Whether you're working with models, views, forms, or other components, <code>super()</code> can greatly simplify the process of working with inheritance in Django.</p>
<p>I hope this article has helped you understand how to use the <code>super()</code> keyword in Django and how it can make working with inheritance in your projects easier and more efficient. If you have any further questions or need additional clarification, don't hesitate to ask.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670995756547/VSHFU90Qf.gif" alt="DudeImJustLearningStanMarshGIF.gif" /></p>
<p>Disclaimer - I am not an expert &amp; still learning. So, there may be some things which i may missed out or may be wrongly explained. As I got any comment about mistakes or figured out somewhere then i will correct it in blog.</p>
]]></content:encoded></item><item><title><![CDATA[Deploy Django App over Ubuntu VPS with Gunicorn + Nginix + PostgreSQL]]></title><description><![CDATA[In this tutorial, we will deploy a Django app over Ubuntu VPS with Gunicorn + Nginix. We are going to use Vultr VPS for this tutorial. You can use any other VPS provider as well.
1. Create a new user

adduser yourusername

usermod -aG sudo youruserna...]]></description><link>https://blog.sorv.dev/deploy-django-app-over-ubuntu-vps-with-gunicorn-nginix-postgresql</link><guid isPermaLink="true">https://blog.sorv.dev/deploy-django-app-over-ubuntu-vps-with-gunicorn-nginix-postgresql</guid><category><![CDATA[deployment]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[django forms]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Wed, 14 Dec 2022 05:23:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670779013693/igLOJ9a3b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-in-this-tutorial-we-will-deploy-a-django-app-over-ubuntu-vps-with-gunicorn-nginix-we-are-going-to-use-vultr-vps-for-this-tutorial-you-can-use-any-other-vps-provider-as-well">In this tutorial, we will deploy a Django app over Ubuntu VPS with Gunicorn + Nginix. We are going to use Vultr VPS for this tutorial. You can use any other VPS provider as well.</h3>
<h3 id="heading-1-create-a-new-user">1. Create a new user</h3>
<ul>
<li><p><code>adduser yourusername</code></p>
</li>
<li><p><code>usermod -aG sudo yourusername</code></p>
</li>
<li><p>logout and login with your new user</p>
</li>
</ul>
<h3 id="heading-2-set-the-hostname">2. Set the HostName</h3>
<ul>
<li><p><code>hostnamectl set-hostname yourdomain.com</code></p>
</li>
<li><p><code>hostname</code> <em># check the hostname</em></p>
</li>
<li><p><code>sudo nano /etc/hosts</code> <em># add the hostname to the hosts file</em></p>
<ul>
<li><code>host_ip_address yourdomain.com</code></li>
</ul>
</li>
</ul>
<h3 id="heading-3-passwordless-login">3. Passwordless login</h3>
<ul>
<li><p>on the local environment:</p>
<ul>
<li><p>generate your ssh key if you don't have one</p>
</li>
<li><p>copy the public key</p>
</li>
</ul>
</li>
<li><p>on the remote environment:</p>
<ul>
<li><p>create a <code>.ssh</code> folder</p>
</li>
<li><p>In that folder, create a file named <code>authorized_keys</code> like this <code>.ssh/authorized_keys</code></p>
</li>
<li><p>paste the public key into the file</p>
</li>
<li><p>each key should be in a separate line. If you have multiple keys, you can paste them all into the file to allow multiple users to log in without a password</p>
</li>
<li><p><code>chmod 700 .ssh</code></p>
</li>
<li><p><code>chmod 600 .ssh/authorized_keys</code></p>
</li>
<li><p><code>sudo nano /etc/ssh/sshd_config</code> <em># change</em> <code>PermitRootLogin</code> &amp; <code>PasswordAuthentication</code> <em>to</em> <code>no</code></p>
</li>
<li><p><code>sudo service ssh restart</code></p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-4-install-andamp-configure-ufw">4. Install &amp; configure UFW</h3>
<pre><code class="lang-bash">sudo apt install ufw
sudo ufw allow ssh
sudo ufw <span class="hljs-built_in">enable</span>
sudo ufw status
</code></pre>
<h3 id="heading-5-install-dependencies">5. Install Dependencies</h3>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> yourproject
sudo apt update
sudo apt install python3.8 python3-pip python3.8-venv python3.8-dev build-essential libpq-dev python3-dev postgresql postgresql-contrib nginx curl
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670995047191/FWGkFP6FI.gif" alt="SpongebobDatabaseGIF.gif" /></p>
<h3 id="heading-6-configure-postgresql"><strong>6. Configure Postgresql</strong></h3>
<pre><code class="lang-bash">sudo -u postgres psql
CREATE DATABASE my_db;
CREATE USER db_usr WITH ENCRYPTED PASSWORD <span class="hljs-string">'some_secure_pass@pass'</span>;
ALTER ROLE db_usr SET client_encoding TO <span class="hljs-string">'utf8'</span>;
ALTER ROLE db_usr SET default_transaction_isolation TO <span class="hljs-string">'read committed'</span>;
GRANT ALL PRIVILEGES ON DATABASE my_db TO db_usr;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670995091693/O_u_5ZDtM.gif" alt="MiniMansionsGIF.gif" /></p>
<h3 id="heading-7-configure-project">7. Configure Project</h3>
<pre><code class="lang-bash">python3.8 -m venv env
pip install --upgrade pip
pip install -r requirements.txt
python manage.py migrate
</code></pre>
<h3 id="heading-8-configure-gunicorn">8. Configure Gunicorn</h3>
<pre><code class="lang-bash">sudo apt install gunicorn
sudo nano /etc/systemd/system/gunicorn.socket
</code></pre>
<p><em>Below is the content, you need to put in this file.</em></p>
<pre><code class="lang-bash">[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target
</code></pre>
<p>we also need a gunicorn service file. so enter to this command to create &amp; edit that service file.</p>
<pre><code class="lang-bash">sudo nano /etc/systemd/system/gunicorn.service
</code></pre>
<p>add the below content to this file.</p>
<pre><code class="lang-bash">[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=saurav
Group=www-data
WorkingDirectory=/home/saurav/trends
EnvironmentFile=/home/saurav/trends/.base.env
ExecStart=/home/saurav/trends/env/bin/gunicorn \
        --access-logfile - \
        --workers 3 \
        --<span class="hljs-built_in">bind</span> unix:/run/gunicorn.sock \
        trends.wsgi:application

[Install]
WantedBy=multi-user.target
</code></pre>
<h3 id="heading-now-run-below-commands">Now Run below commands -</h3>
<pre><code class="lang-bash">sudo systemctl start gunicorn.socket
sudo systemctl <span class="hljs-built_in">enable</span> gunicorn.socket
sudo systemctl status gunicorn.socket
file /run/gunicorn.sock
</code></pre>
<h4 id="heading-note-if-you-get-an-error-like-this-debug-it-with-sudo-journalctl-u-gunicornsocket">Note: If you get an error like this: - debug it with <code>sudo journalctl -u gunicorn.socket</code></h4>
<h3 id="heading-8-configure-nginix">8. Configure Nginix</h3>
<p><code>sudo nano /etc/nginx/sites-available/trends</code></p>
<p>put below content in this file.</p>
<pre><code class="lang-bash">server {
    listen 8000;
    server_name 139.84.163.233;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/saurav/trends;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}
</code></pre>
<p><code>sudo ln -s /etc/nginx/sites-available/trends /etc/nginx/sites-enabled</code></p>
<p><code>sudo nginx -t</code></p>
<p><code>sudo systemctl restart nginx</code></p>
<p><code>sudo ufw allow 'Nginx Full'</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670994690767/BQyGs69EQ.gif" alt="UnamusedCatGIF (2).gif" /></p>
<h2 id="heading-congratulations-if-all-steps-are-executed-successfully-then-your-website-should-be-live-now">Congratulations 🎉🎊 if all steps are executed successfully, then your website should be live now.</h2>
<p>Note: Some commands to reload/modify/debug services:</p>
<pre><code class="lang-bash">sudo systemctl restart gunicorn  <span class="hljs-comment"># restart gunicorn</span>
sudo systemctl daemon-reload  
sudo tail -F /var/<span class="hljs-built_in">log</span>/nginx/error.log  <span class="hljs-comment"># check nginix error logs</span>
sudo systemctl status postgresql  <span class="hljs-comment"># start your psql db server</span>
sudo systemctl restart gunicorn.socket gunicorn.service  <span class="hljs-comment"># restart gunicorn</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670995803783/qOt7a7Ugx.gif" alt="LetsFigureThisOutSolveGIF.gif" /></p>
<h2 id="heading-if-you-are-still-having-problem-then-check-this-great-article-by-digital-ocean">If you are still having problem then check this great article by Digital Ocean.</h2>
<p><a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04">https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670995756547/VSHFU90Qf.gif" alt="DudeImJustLearningStanMarshGIF.gif" /></p>
<p>Disclaimer - I am not an expert &amp; still learning. So, there may be some things which i may missed out or may be wrongly explained. As I got any comment about mistakes or figured out somewhere then i will correct it in blog.</p>
]]></content:encoded></item><item><title><![CDATA[Dockerize an Django Based app along Postgres + Redis + ElasticSearch 🚀]]></title><description><![CDATA[Docker is a wonderful tool to containerise your website. A website have many micro services & inter dependent in most of the cases. Things even get more worse when we try to setup our app on different PC / Operating System. Docker is a lifesaver in t...]]></description><link>https://blog.sorv.dev/dockerize-an-django-based-app-along-postgres-redis-elasticsearch</link><guid isPermaLink="true">https://blog.sorv.dev/dockerize-an-django-based-app-along-postgres-redis-elasticsearch</guid><category><![CDATA[Docker]]></category><category><![CDATA[Docker compose]]></category><category><![CDATA[Django]]></category><category><![CDATA[Dockerize Django application]]></category><category><![CDATA[elasticsearch]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Wed, 09 Nov 2022 15:27:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668005110826/0khOAsES3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Docker is a wonderful tool to containerise your website. A website have many micro services &amp; inter dependent in most of the cases. Things even get more worse when we try to setup our app on different PC / Operating System. Docker is a lifesaver in this situation and solves the very popular problem which almost every programmer faces when shipping the code to another platform. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668005656296/2JCAvjdH5.jpg" alt="4WwcmWUBCXmZ4iV0J_esaqE7O_GVYjON52dbVkEv5_c-2849584195.jpg" /></p>
<p>🚧 Before Moving ahead, you must have a basic knowledge of Docker &amp; Docker Compose.</p>
<p>Here i am going to use my already built django based app <a target="_blank" href="https://github.com/selftaughtdev-me/django-flix-video-streaming">Movie Api</a> which is using Postgres, Redis &amp; ElasticSearch.</p>
<p>I will use a Docker compose file to keep every service in order as we can only starts our django server after postgres is ready and we can only starts elastic search after redis is ready. so these services are interdependent &amp; I am going to link them properly so that they can start in an order, rather than just running independently and breaking things.</p>
<p>Before I start Explaining, have a look at my final Docker compose file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668006317898/Q3AenYKgb.gif" alt="AwLookGIF.gif" /></p>
<pre><code class="lang-python">version: <span class="hljs-string">'3.8'</span>

services:
  django_server:
    build: .
    working_dir: /usr/src/app
    command: &gt;
      sh -c <span class="hljs-string">"python manage.py wait_for_db &amp;&amp;
            python manage.py wait_for_elk &amp;&amp;
            python manage.py migrate &amp;&amp;
            apt install -y curl &amp;&amp;
            echo 'sending curl to Elastic hosts' &amp;&amp;
            curl -X GET ${ELK_CONNECTION_PROTOCOL}://${ELK_HOST}:${ELK_PORT} &amp;&amp;
            python manage.py generate_test_data 1000 &amp;&amp;
            gunicorn src.wsgi:application --bind 0.0.0.0:8000"</span>
    ports:
      - <span class="hljs-number">8000</span>:<span class="hljs-number">8000</span>
    env_file: .env
    depends_on:
      - elasticsearch7
      - postgresql_db
    container_name: django_server

  elasticsearch7:
    image: elasticsearch:<span class="hljs-number">7.17</span><span class="hljs-number">.5</span>
    ports:
      - ${ELK_PORT}:${ELK_PORT}
      - <span class="hljs-number">9300</span>:<span class="hljs-number">9300</span>
    container_name: ${ELK_HOST}
    restart: always
    environment:
      - discovery.type=single-node
      - node.name=django_flix
      - <span class="hljs-string">"ES_JAVA_OPTS=-Xms512m -Xmx512m"</span>

  postgresql_db:
    image: postgres
    ports:
      - <span class="hljs-number">5432</span>:<span class="hljs-number">5432</span>
    restart: always
    env_file: .env
    container_name: ${POSTGRES_HOST}
</code></pre>
<p>In above Code, I have added <code>depends_on</code> to make services wait for another one before starting up. I have not very good understanding of docker, so i don't know why the django server always get started before even fully starting the dependent services. So, to overcame this issue i made a custom command in django which stops the server until the Database and ElasticSearch are fully started. </p>
<p>here is code for both of the files where i write custom commands.</p>
<pre><code class="lang-python"><span class="hljs-string">"""
Django Command to wait for DB to be available
"""</span>
<span class="hljs-keyword">import</span> time

<span class="hljs-keyword">from</span> django.core.management <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> django.db.utils <span class="hljs-keyword">import</span> OperationalError <span class="hljs-keyword">as</span> DjangoDbUtilsOperationalError
<span class="hljs-keyword">from</span> psycopg2 <span class="hljs-keyword">import</span> OperationalError <span class="hljs-keyword">as</span> Psycopg2OperationalError


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    <span class="hljs-string">"""Django command to pause execution until database is available"""</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        self.stdout.write(<span class="hljs-string">"Waiting for database..."</span>)
        db_is_up = <span class="hljs-literal">False</span>
        <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> db_is_up:
            <span class="hljs-keyword">try</span>:
                <span class="hljs-comment"># try to connect with db</span>
                self.check(databases=[<span class="hljs-string">"default"</span>])
                db_is_up = <span class="hljs-literal">True</span>
                self.stdout.write(self.style.SUCCESS(<span class="hljs-string">"Database is available"</span>))
            <span class="hljs-keyword">except</span> (Psycopg2OperationalError, DjangoDbUtilsOperationalError):
                self.stdout.write(
                    self.style.WARNING(
                        <span class="hljs-string">"Database is still not ready. will try in 1 second"</span>
                    )
                )
                time.sleep(<span class="hljs-number">1</span>)
</code></pre>
<p>And this one below is for waiting for elastic search to start.</p>
<pre><code class="lang-python"><span class="hljs-string">"""
Django Command to wait for DB to be available
"""</span>
<span class="hljs-keyword">import</span> time

<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">from</span> django.core.management <span class="hljs-keyword">import</span> BaseCommand
<span class="hljs-keyword">from</span> elasticsearch <span class="hljs-keyword">import</span> Elasticsearch


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span>(<span class="hljs-params">BaseCommand</span>):</span>
    <span class="hljs-string">"""Django command to pause execution until database is available"""</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">self, *args, **options</span>):</span>
        self.stdout.write(<span class="hljs-string">"Waiting for ElasticSearch..."</span>)
        elk_is_up = <span class="hljs-literal">False</span>
        host = settings.ELK_HOST
        port = settings.ELK_PORT
        <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> elk_is_up:
            <span class="hljs-keyword">try</span>:
                <span class="hljs-comment"># try to connect with db</span>
                self.stdout.write(
                    self.style.WARNING(
                        <span class="hljs-string">"trying to connect with elk at {}:{}"</span>.format(host, port)
                    )
                )
                es = Elasticsearch([{<span class="hljs-string">"host"</span>: host, <span class="hljs-string">"port"</span>: port}])
                es.cluster.health(wait_for_status=<span class="hljs-string">"yellow"</span>, request_timeout=<span class="hljs-number">1</span>)
                self.stdout.write(self.style.SUCCESS(<span class="hljs-string">"✅ ElasticSearch is available"</span>))
                elk_is_up = <span class="hljs-literal">True</span>
            <span class="hljs-keyword">except</span> Exception:
                self.stdout.write(
                    self.style.WARNING(
                        <span class="hljs-string">"❌ ElasticSearch is still not ready. will try in 5 second"</span>
                    )
                )
                time.sleep(<span class="hljs-number">5</span>)
</code></pre>
<p>I also uses a enviroment file so that i can pass these values to docker compose file instead of defining them in the key value pair in docker compose file.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> DEBUG=1
<span class="hljs-built_in">export</span> POSTGRES_DB=<span class="hljs-string">"postgres"</span>
<span class="hljs-built_in">export</span> POSTGRES_USER=<span class="hljs-string">"postgres"</span>
<span class="hljs-built_in">export</span> POSTGRES_PASSWORD=<span class="hljs-string">"postgres"</span>
<span class="hljs-built_in">export</span> POSTGRES_HOST=<span class="hljs-string">"postgresql_db_latest"</span>
<span class="hljs-built_in">export</span> POSTGRES_PORT=5432
<span class="hljs-built_in">export</span> ES_JAVA_OPTS=<span class="hljs-string">"-Xms8g -Xmx8g"</span>
<span class="hljs-built_in">export</span> ELK_HOST=<span class="hljs-string">"elasticsearch_v7"</span>
<span class="hljs-built_in">export</span> ELK_PORT=<span class="hljs-string">"9200"</span>
<span class="hljs-built_in">export</span> ELK_INDEX_NAME=<span class="hljs-string">"django_flix"</span>
<span class="hljs-built_in">export</span> ELK_CONNECTION_PROTOCOL=<span class="hljs-string">"http"</span>
</code></pre>
<p>This way I was able to link these services and starts them in a order.</p>
<p>Helpful Links</p>
<ul>
<li><a target="_blank" href="https://www.djangoproject.com/">Django</a></li>
<li><a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-elasticsearch-on-ubuntu-20-04">ElasticSearch 7 Installation guide</a></li>
<li><a target="_blank" href="https://docs.djangoproject.com/en/4.1/howto/custom-management-commands/">Django Custom Commands</a></li>
<li><a target="_blank" href="https://docs.docker.com/compose/">Docker Compose</a></li>
<li><a target="_blank" href="https://github.com/selftaughtdev-me/django-flix-video-streaming">Link to Code on GitHub</a></li>
</ul>
<p>Disclaimer - I am not an expert &amp; still learning. So, there may be some things which i may missed out or may be wrongly explained. As I got any comment about mistakes or figured out somewhere then i will correct it in blog. </p>
]]></content:encoded></item><item><title><![CDATA[Extract Keyword from a Website using Python]]></title><description><![CDATA["""
Run this command in Terminal after activating environment:
python -c "import nltk;nltk.download('stopwords')"
python -c "import nltk;nltk.download('punkt')"
"""

from bs4 import BeautifulSoup
import requests
from rake_nltk import Rake

rake = Rak...]]></description><link>https://blog.sorv.dev/extract-keyword-from-a-website-using-python</link><guid isPermaLink="true">https://blog.sorv.dev/extract-keyword-from-a-website-using-python</guid><category><![CDATA[webscraping ]]></category><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[Keywords]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Mon, 17 Oct 2022 12:57:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/OqtafYT5kTw/upload/v1666011298864/hpTywrXfY.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<pre><code class="lang-python"><span class="hljs-string">"""
Run this command in Terminal after activating environment:
python -c "import nltk;nltk.download('stopwords')"
python -c "import nltk;nltk.download('punkt')"
"""</span>

<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> rake_nltk <span class="hljs-keyword">import</span> Rake

rake = Rake()  <span class="hljs-comment"># ML library to extract keywords from text</span>

url_to_scrape = <span class="hljs-string">"https://html_programmer.com"</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_text</span>(<span class="hljs-params">url</span>):</span>
    <span class="hljs-comment"># get the html content</span>
    html_content = requests.get(url).text
    <span class="hljs-comment"># parse the html content</span>
    soup = BeautifulSoup(html_content, <span class="hljs-string">"html.parser"</span>)
    <span class="hljs-keyword">return</span> soup.get_text()


website_text = get_text(url_to_scrape)
rake.extract_keywords_from_text(website_text)
keywords = rake.get_ranked_phrases_with_scores()

<span class="hljs-keyword">for</span> score, keyword <span class="hljs-keyword">in</span> keywords:
    print(<span class="hljs-string">f"<span class="hljs-subst">{score}</span> -&gt; <span class="hljs-subst">{keyword}</span>"</span>)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Make your API to return only Required Fields]]></title><description><![CDATA[Hey Folks,
In this Blog, I am going to show you how can return only those fields from the API which user have requested by passing them in URL as parameters. this way your API users can only request those fields which they need & this will remove som...]]></description><link>https://blog.sorv.dev/make-your-api-to-return-only-required-fields</link><guid isPermaLink="true">https://blog.sorv.dev/make-your-api-to-return-only-required-fields</guid><category><![CDATA[REST API]]></category><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[serialization]]></category><category><![CDATA[django forms]]></category><dc:creator><![CDATA[Saurav Sharma]]></dc:creator><pubDate>Sat, 15 Oct 2022 07:55:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665814048048/z0K-pHpvv.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey Folks,</p>
<p>In this Blog, I am going to show you how can return only those fields from the API which user have requested by passing them in URL as parameters. this way your API users can only request those fields which they need &amp; this will remove some unnecessary clutter from your API response.</p>
<p>if you don't understand about URL parameters then head toward <a target="_blank" href="https://stackoverflow.com/questions/5095887/how-do-i-pass-a-url-with-multiple-parameters-into-a-url">this link for some reference.</a></p>
<p>if you are a visual learner then checkout the video version of this blog. </p>
<div class="hn-embed-widget" id="yt-req-fields-api"></div><p>For Reference, I will be using a demo API which I created in the previous Blog. you can find the code for that API <a target="_blank" href="https://selftaughtdev.me/build-blazing-fast-rest-api-using-django-elasticsearch-haystack">here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665820308211/HjDy5XIC8.gif" alt="LetTheGamesBeginPanicGIF.gif" /></p>
<p>Now, first pass some params in the URL Bar of your browser &amp; then hit enter.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665818029185/Y5qnnXExm.png" alt="image.png" /></p>
<p>As you can see, even after passing some fields in URL Bar, the API is still returning all fields. Which is obvious as we haven't implemented any logic for that yet.</p>
<p>Now, let's implement that logic. For that, we will be using our <code>MovieHaystackSerializer</code> from previous Blog Code.
Now you will see some different code than what you usually write in your Serializer. Just Ignore new things as of now &amp; only focus on <code>__init__</code> and <code>fields</code> of our Serializer. So, first, we will edit our <code>MovieHayStackSerializer</code> by overiding the <code>__init__</code> method.</p>
<pre><code class="lang-python"><span class="hljs-comment"># 3rd party imports</span>
<span class="hljs-keyword">from</span> drf_haystack.serializers <span class="hljs-keyword">import</span> HaystackSerializer

<span class="hljs-comment"># local imports</span>
<span class="hljs-keyword">from</span> .search_indexes <span class="hljs-keyword">import</span> MovieIndex


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MovieHayStackSerializer</span>(<span class="hljs-params">HaystackSerializer</span>):</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        <span class="hljs-comment"># The `index_classes` attribute is a list of which search indexes</span>
        <span class="hljs-comment"># we want to include in the search.</span>
        index_classes = [MovieIndex]

        <span class="hljs-comment"># The `fields` contains all the fields we want to include.</span>
        <span class="hljs-comment"># <span class="hljs-doctag">NOTE:</span> Make sure you don't confuse these with model attributes. These</span>
        <span class="hljs-comment"># fields belong to the search index!</span>
        fields = [
            <span class="hljs-string">"title"</span>,
            <span class="hljs-string">"description"</span>,
            <span class="hljs-string">"year"</span>,
            <span class="hljs-string">"rating"</span>,
            <span class="hljs-string">"global_ranking"</span>,
            <span class="hljs-string">"length"</span>,
            <span class="hljs-string">"revenue"</span>,
            <span class="hljs-string">"genre"</span>,
            <span class="hljs-string">"country"</span>,
            <span class="hljs-string">"director"</span>,
        ]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
        super(MovieHayStackSerializer, self).__init__(*args, **kwargs)
        <span class="hljs-comment"># get the query params</span>
        fields_in_url_params = self.context[<span class="hljs-string">"request"</span>].GET.get(<span class="hljs-string">"fields"</span>, <span class="hljs-literal">None</span>)
        <span class="hljs-keyword">if</span> fields_in_url_params:
            <span class="hljs-comment"># split the params from comma to make a list</span>
            fields_in_url_parmas_list = fields_in_url_params.split(<span class="hljs-string">","</span>)
            existing_fields = set(self.fields)  <span class="hljs-comment"># -&gt; keys of all fields</span>

            <span class="hljs-comment"># get all the fields which are not in params but declared in meta class above</span>
            <span class="hljs-comment"># our_fields = {'title', 'description', 'year', '....'}</span>
            <span class="hljs-comment"># fields_from_urls = {'title', 'year'}</span>
            <span class="hljs-comment"># operation = our_fields - fields_from_url =&gt; {'description', '.....'}</span>
            fields_to_remove = existing_fields - set(fields_in_url_parmas_list)

            <span class="hljs-comment"># now since we have fields </span>
            <span class="hljs-keyword">for</span> field <span class="hljs-keyword">in</span> fields_to_remove:
                self.fields.pop(field)  <span class="hljs-comment"># remove this field</span>
</code></pre>
<p>Now, let's see what we have done here. First, we have overrided the <code>__init__</code> method of our Serializer. In this method, we are getting the query params from the URL &amp; then we are splitting them by comma to make a list. After that, we are getting all the fields which are declared in our <code>Meta</code> class. Now, we are getting the difference between these two lists. The difference will be the fields which we have to remove from our Serializer. After that, we are removing those fields from our Serializer.</p>
<p>Now, let's see what happens when we pass some fields in URL Bar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665819195494/wVmX6DWKO.gif" alt="CrossFingersPrayingGIF.gif" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665818780150/Xk2GHKIA1.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665819894987/eNYQRVFMX.gif" alt="TheKingOfRandomSuccessGIF.gif" /></p>
<p>As you can see, now we are only getting those fields which we have passed in URL Bar. This is how you can return only those fields from the API which user have requested by passing them in URL as parameters.
Link for Repo - https://github.com/selftaughtdev-me/movie-search-api</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665819528638/XqoQzo6Bl.gif" alt="ThatsAllLukeTillersonGIF.gif" /></p>
<p>If you have any questions or suggestions, feel free to comment below. I will be happy to help you. Also, if you like this Blog, then please share it with your friends &amp; colleagues. you can also follow me on <a target="_blank" href="https://twitter.com/selftaughtdev_">Twitter</a> &amp; <a target="_blank" href="https://www.linkedin.com/in/selftaughtdev-me/">LinkedIn</a> for getting updates about my new Blogs.</p>
]]></content:encoded></item></channel></rss>