<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dinesh S</title>
    <description>The latest articles on DEV Community by Dinesh S (@dineshs91).</description>
    <link>https://dev.to/dineshs91</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F762452%2Fd641af2b-3bf8-445e-b315-4301ccc47c33.png</url>
      <title>DEV Community: Dinesh S</title>
      <link>https://dev.to/dineshs91</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dineshs91"/>
    <language>en</language>
    <item>
      <title>Build side projects instead of tutorials</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Mon, 28 Nov 2022 08:54:52 +0000</pubDate>
      <link>https://dev.to/dineshs91/build-side-projects-instead-of-tutorials-3cba</link>
      <guid>https://dev.to/dineshs91/build-side-projects-instead-of-tutorials-3cba</guid>
      <description>&lt;h2&gt;
  
  
  Why building side projects is a better way to learn programming than following tutorials.
&lt;/h2&gt;

&lt;p&gt;You want to get into programming and you started by following tutorials. They are fine in the initial days, when you are just starting out. But after you get comfortable, you have to start building projects to see more benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here are a few reasons why building side projects is better
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When you build projects, you learn by doing and you gain practical experience that can't be gained by following tutorials.&lt;/li&gt;
&lt;li&gt;Building projects also forces you to think creatively and solve problems on your own, which is an essential skill for any programmer.&lt;/li&gt;
&lt;li&gt;By contrast, tutorials can often be too simplistic or too specific, and they don't always give you a chance to practice your skills in a real-world context.&lt;/li&gt;
&lt;li&gt;Projects also tend to be more engaging and enjoyable than tutorials, so you'll be more motivated to stick with them (and learning programming) in the long run.&lt;/li&gt;
&lt;li&gt;Building projects, leaves room for self exploration and fosters creativity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits of side projects.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Working on side projects can help you learn new programming concepts and sharpen your skills.&lt;/li&gt;
&lt;li&gt;Completing a side project can give you a sense of accomplishment and boost your confidence.&lt;/li&gt;
&lt;li&gt;Working on side projects can help you explore different programming languages and tools.&lt;/li&gt;
&lt;li&gt;Side projects can also be a great way to land your dream job or get freelance clients.&lt;/li&gt;
&lt;li&gt;You have something to show in a job interview.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to get started with building your own projects.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Start small. You can build simple projects at first.&lt;/li&gt;
&lt;li&gt;Find inspiration for projects from things you're interested in or want to learn more about.&lt;/li&gt;
&lt;li&gt;Try to automate a manual workflow that you do everyday.&lt;/li&gt;
&lt;li&gt;Use online resources and Google searches to help you figure out how to build what you want.&lt;/li&gt;
&lt;li&gt;Build a tool that you want for yourself if it doesn't exist already. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Are you still stuck with tutorials? Now is the time to go build something. Do you need help, feel FREE to DM me on &lt;a href="https://twitter.com/SDinesh91"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>10 Advanced Git workflows that will come in handy</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Mon, 07 Nov 2022 09:13:38 +0000</pubDate>
      <link>https://dev.to/dineshs91/10-advanced-git-commands-that-will-come-in-handy-14e6</link>
      <guid>https://dev.to/dineshs91/10-advanced-git-commands-that-will-come-in-handy-14e6</guid>
      <description>&lt;p&gt;We all know the common Git commands which we use everyday, like &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;Here are 10 less known commands which can be very useful. &lt;/p&gt;

&lt;p&gt;1⃣ &lt;strong&gt;Undo a git rebase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You did a rebase &amp;amp; now you want to undo it. Do git reflog and find the point where you want to go. (Ex: HEAD@{10}) You will find something like the below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reflog
&amp;lt;sha&amp;gt; HEAD@{9}: rebase: action
&amp;lt;sha&amp;gt; HEAD@{10}: commit: action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;HEAD@{9}&lt;/code&gt; is the point where rebase was done. So to revert that, we have to do a hard reset to the previous point.&lt;/p&gt;

&lt;p&gt;Use the value to do a hard reset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --hard HEAD@{10}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rebase you did is undone.&lt;/p&gt;

&lt;p&gt;2⃣ &lt;strong&gt;Hunt a mysterious bug in your git history&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This command uses a binary search algorithm to find which commit in your project’s history introduced a bug. You use it by first choosing a "bad" commit that is known to contain the bug, and a "good" commit that is known to be clean, before the bug was introduced. Git bisect picks a commit between those two points and asks you whether the selected commit is "good" or "bad". It continues narrowing down the range until it finds the exact commit that introduced the change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git bisect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3⃣ &lt;strong&gt;Modify the commit message of a specific commit&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git rebase -i head~2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command display's interactive rebase for the last 2 commits from head. Choose the number based on where your commit lies from head. If it is the 2th commit, choose head~2. You will get something like below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick &amp;lt;sha&amp;gt; &amp;lt;commit message&amp;gt;
pick &amp;lt;sha&amp;gt; &amp;lt;commit message&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the command to &lt;code&gt;edit&lt;/code&gt; instead of &lt;code&gt;pick&lt;/code&gt; from the above, corresponding to the commit you want to modify&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick &amp;lt;sha&amp;gt; &amp;lt;commit message&amp;gt;
edit &amp;lt;sha&amp;gt; &amp;lt;commit message&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go ahead &amp;amp; save and the rebase will stop at the 2nd commit. You can amend the commit to change the commit message. Do git rebase --continue to finish the rebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git commit --amend
git rebase --continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4⃣ &lt;strong&gt;Amend last commit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This comes in very handy if you want to change the commit message of the last commit. It can also be used to add new changes to the previous commit, instead of creating a new commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git commit --amend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5⃣ &lt;strong&gt;Recover a dropped stash&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have dropped a stash, but now you want it back. If you know the hash of the dropped stash you can do the below to retrieve it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git stash apply $stash_hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you drop a stash, a hash of it is printed on the screen. If you haven't closed your terminal, you can locate it by scrolling to the top of your terminal window. If you can't find it, execute the below command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open gitk (git gui). To spot stash commits, look for commit messages that look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WIP on somebranch: commithash Some old commit message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you know the hash of the commit you want, you can apply it as a stash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git stash apply $stash_hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6⃣ &lt;strong&gt;Copy a file from one branch to another branch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to copy an entire file from one branch into another branch, do the below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout bugfix README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bugfix&lt;/code&gt; is the source branch where the file &lt;code&gt;README.md&lt;/code&gt; is located. &lt;/p&gt;

&lt;p&gt;Execute the above command from the branch (destination), where you want the file to be copied.&lt;/p&gt;

&lt;p&gt;7⃣ &lt;strong&gt;Display the changes in a particular commit or a stash&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git show &amp;lt;sha&amp;gt;
git show stash{0}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;8⃣ &lt;strong&gt;Get the number of commits (Commit count)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To get the count for a specific revision&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git rev-list --count &amp;lt;revision&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example &lt;code&gt;git rev-list --count main&lt;/code&gt; will display the commit count in &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;To get the count across all branches&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git rev-list --all --count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;9⃣ &lt;strong&gt;Revert a specific commit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get the  of the commit you want to revert and do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git revert &amp;lt;sha&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔟 &lt;strong&gt;Rename a local branch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to rename a branch that is different from the current branch you are in, do this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git branch -m &amp;lt;oldname&amp;gt; &amp;lt;newname&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to rename the current branch you are in, you can do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git branch -m &amp;lt;newname&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>git</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built my own Tweet scheduler which costs less than 1$ per month</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Sat, 29 Oct 2022 06:20:11 +0000</pubDate>
      <link>https://dev.to/dineshs91/i-built-my-own-tweet-scheduler-which-costs-less-than-1-per-month-5gbd</link>
      <guid>https://dev.to/dineshs91/i-built-my-own-tweet-scheduler-which-costs-less-than-1-per-month-5gbd</guid>
      <description>&lt;p&gt;I wanted a simple scheduler that will post tweets from my Notion database to Twitter. &lt;/p&gt;

&lt;p&gt;I wanted an app without any bells &amp;amp; whistles, just a dumb scheduler. I did not want to pay 10$/mo just for that. So as any developer would do, I decided to write my own scheduler 😂. &lt;/p&gt;

&lt;p&gt;Here's how I did it.&lt;/p&gt;

&lt;p&gt;I wrote a script &lt;code&gt;tweeter.js&lt;/code&gt; which does the following&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch content from a Notion database.&lt;/li&gt;
&lt;li&gt;Prepare the tweet&lt;/li&gt;
&lt;li&gt;Send it to Twitter based on the scheduled time slot&lt;/li&gt;
&lt;li&gt;Mark the tweet as posted in Notion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have hosted the scheduler in AWS and it runs as a container in ECS. &lt;/p&gt;

&lt;p&gt;I use Amazon EventBridge rule to create a new container every 30 minutes. This container when started executes &lt;code&gt;tweeter.js&lt;/code&gt; and terminates. This way, I don't have a server running all the time, which in turn results in reduced costs.&lt;/p&gt;

&lt;p&gt;It costs less than 1$ a month and I can schedule as many tweets/threads I want. &lt;/p&gt;

&lt;p&gt;One caveat is that the tweets schedule time is not accurate to the specific minute. It depends on the frequency at which a new container is created. I have set it to 30 minutes, so if you schedule a tweet between 10:00 &amp;amp; 10:30 all of them would be tweeted at the same time. &lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;ECS infrastructure setup - &lt;a href="https://github.com/vthub/scheduled-ecs-task"&gt;https://github.com/vthub/scheduled-ecs-task&lt;/a&gt;&lt;/p&gt;

</description>
      <category>twitter</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why do we need coding standards?</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Wed, 29 Jun 2022 14:36:46 +0000</pubDate>
      <link>https://dev.to/dineshs91/why-do-we-need-coding-standards-1806</link>
      <guid>https://dev.to/dineshs91/why-do-we-need-coding-standards-1806</guid>
      <description>&lt;p&gt;Why do we need coding standards?&lt;/p&gt;

&lt;p&gt;Many beginner developers ask me why they should worry about coding standards when their code works as expected.&lt;/p&gt;

&lt;p&gt;Here are a few important points. 👇 &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Consistency
&lt;/h3&gt;

&lt;p&gt;Using similar coding styles throughout the codebase is a win for everyone on the team. It reduces the time to think &amp;amp; to decide how to name a variable/class/function or how to structure code. Every developer who codes doesn't have to spend time thinking.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Maintainability
&lt;/h3&gt;

&lt;p&gt;Codebases are mostly contributed by a lot of developers and that keeps changing. Maintaining a constant standard throughout makes it easy for anyone to get started and contribute without friction. Having a set standard helps to keep code complexity in check.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Clean code
&lt;/h3&gt;

&lt;p&gt;The code looks clean. Imagine opening a codebase just to find code written haphazardly. It would be a nightmare to work on such a codebase. This eventually becomes tech debt.&lt;/p&gt;

&lt;p&gt;Coding standards that are enforced, have a cleaner codebase than others.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Developer sanity
&lt;/h3&gt;

&lt;p&gt;If every developer follows their own standards, the codebase would be a mess. It would be very frustrating for another developer to fix bugs or complete features started by others, because of different coding styles.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Reduced development time
&lt;/h3&gt;

&lt;p&gt;When there is a consistent standard, the code is readable and familiar, which saves a lot of time. Developers can start coding right away.&lt;/p&gt;

&lt;p&gt;Reviewing Pull Requests becomes easier as well. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Testing pyramid in software development</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Sun, 23 Jan 2022 10:17:49 +0000</pubDate>
      <link>https://dev.to/dineshs91/testing-pyramid-in-software-development-nef</link>
      <guid>https://dev.to/dineshs91/testing-pyramid-in-software-development-nef</guid>
      <description>&lt;p&gt;We have all been hearing about the importance of writing tests and test-driven development.&lt;/p&gt;

&lt;p&gt;✅ Here's how you can write better tests by implementing the testing pyramid strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  A testing pyramid is made up of 3 layers
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Unit Tests&lt;/li&gt;
&lt;li&gt;Integration Tests&lt;/li&gt;
&lt;li&gt;End to End Tests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HGxk5DDO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7sh80sa4oru07efiro21.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HGxk5DDO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7sh80sa4oru07efiro21.jpeg" alt="Image description" width="880" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Testing pyramid tells you what type of tests you should be more focussed on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit tests
&lt;/h3&gt;

&lt;p&gt;A unit test verifies that an individual piece of code functions as expected. They don't have any external dependencies.&lt;/p&gt;

&lt;p&gt;Unit tests form the base of the pyramid. Ideally they should constitute bulk of your tests.&lt;/p&gt;

&lt;p&gt;They are quite fast to execute, hence devs are encouraged to run it as often as required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration
&lt;/h3&gt;

&lt;p&gt;Next layer on the pyramid are the integration tests.&lt;/p&gt;

&lt;p&gt;Integration tests validate how a piece of code functions when it interacts with other code. These tests communicate with external dependencies like database's &amp;amp; external API's.&lt;/p&gt;

&lt;p&gt;They are slow compared to unit tests. They also need the external services to be running.&lt;/p&gt;

&lt;h3&gt;
  
  
  End to End (E2E)
&lt;/h3&gt;

&lt;p&gt;At the top of the pyramid, we have End 2 End tests.&lt;/p&gt;

&lt;p&gt;These tests interact with pages just like a user would, for example by clicking buttons and typing into text boxes, and they check that pages respond appropriately from the user's perspective.&lt;/p&gt;

&lt;p&gt;They test the functioning of an app in its entirety. They are the slowest to run and given its dependency over multiple things, they are non-deterministic in nature. Ideally, you should be having only a few of these.&lt;/p&gt;

&lt;p&gt;They are non-deterministic, the automation software must wait for the browser to complete the page load and for the particular element to appear.&lt;/p&gt;

&lt;p&gt;For example, suppose that you write a test that clicks a button to open a modal and then clicks a button inside the modal to close it. Sometimes, the modal will open before the test tries to click the close button, so the test will pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;We've looked at the types of tests above. &lt;/p&gt;

&lt;p&gt;At a broad level, there are 2 aspects of a test type.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speed&lt;/li&gt;
&lt;li&gt;Test surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see that speed is inversely proportional to the test area. Unit tests cover a short area, hence they are fast to run. But they cannot guarantee the functioning of a feature from the perspective of a user.&lt;/p&gt;

&lt;p&gt;On the other hand E2E tests, validate the functioning of an entire feature as an end user would use it, but they are very slow to run and have lot of dependencies.&lt;/p&gt;

&lt;p&gt;A good test suite will have a mix of the 3 types of tests outlined above. Unit tests are more reliable and fast, on the other hand E2E tests verify the functioning of an entire app, but are slow and non-deterministic.&lt;/p&gt;

</description>
      <category>software</category>
      <category>testing</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How I built SetProgress - Part 4 Webhooks</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Thu, 30 Dec 2021 12:08:41 +0000</pubDate>
      <link>https://dev.to/dineshs91/how-i-built-setprogress-part-4-webhooks-4m95</link>
      <guid>https://dev.to/dineshs91/how-i-built-setprogress-part-4-webhooks-4m95</guid>
      <description>&lt;p&gt;In the last few posts I have explained how I built waitlist, progress bar &amp;amp; the automation. In this post, I will talk about how &amp;amp; why I added Webhooks to the app.&lt;/p&gt;

&lt;p&gt;Webhook Integration&lt;br&gt;
  &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1WwE5pJi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkzhqe76ac0akh6b54lm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1WwE5pJi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkzhqe76ac0akh6b54lm.png" width="880" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous post I wrote about how I automated 100DaysOfCode progress. That automation is very limited as it just works only based on HashTags. The idea of adding a Webhooks endpoint was born from there.&lt;/p&gt;

&lt;p&gt;Implementing this is quite simple. All I have to do is to expose an api &lt;code&gt;/api/users/external-progress&lt;/code&gt; which accepts &lt;code&gt;currentProgress&lt;/code&gt; in the request body. Users have to send their currentProgress to this endpoint to update their progress.&lt;/p&gt;

&lt;p&gt;The api is pretty simple, but how do we identify the user whose progress is to be updated?&lt;/p&gt;

&lt;p&gt;Here is where the unique secret comes in handy. Each user gets a secret which is unique to them. They have to set this key in the header of the endpoint. You might ask why secret and why not user id? User Id can be obtained easily which allows a bad actor to update the progress of another user. We don't want that, hence the secret. If user feels that their secret is compromised, then they can generate a new one from the settings page.&lt;/p&gt;

&lt;p&gt;Now that we have an endpoint, users can make use of NoCode tools like Zapier, Integromat, Integrately etc to automate their progress updates. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Update MRR&lt;/li&gt;
&lt;li&gt;Update your running mileage&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>sideprojects</category>
      <category>nextjs</category>
      <category>twitter</category>
    </item>
    <item>
      <title>How I built SetProgress - Part 3 Automation</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Sat, 25 Dec 2021 03:18:32 +0000</pubDate>
      <link>https://dev.to/dineshs91/how-i-built-setprogress-part-3-automation-54k2</link>
      <guid>https://dev.to/dineshs91/how-i-built-setprogress-part-3-automation-54k2</guid>
      <description>&lt;p&gt;In the previous post, I explained how I built SetProgress.&lt;/p&gt;

&lt;p&gt;In this post, I am going to explain how I automated progress updates. &lt;/p&gt;

&lt;p&gt;Automation was not part of my initial plan. I just wanted to create a simple app which allows a user to design &amp;amp; push a progress bar to Twitter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/sumusiriwardana"&gt;Sumudu Siriwardana&lt;/a&gt; asked me if the app could calculate &amp;amp; update progress for her #100DaysOfCode challenge.&lt;/p&gt;

&lt;p&gt;I thought about it, and I realized that the app doesn't offer much automation. User has to update their progress manually everyday or every week. &lt;/p&gt;

&lt;h3&gt;
  
  
  Automating #100DaysOfCode
&lt;/h3&gt;

&lt;p&gt;Let me give you a bit of context on &lt;a href="https://www.100daysofcode.com/"&gt;100DaysOfCode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It has 2 rules&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code minimum an hour every day for the next 100 days.&lt;/li&gt;
&lt;li&gt;Tweet your progress every day with the #100DaysOfCode hashtag. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anyone who is doing this challenge will tweet everyday tagging 100DaysOfCode and a little bit about what they did that day. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can fetch all the tweets of a user and check if any one of those have the hashtag 100DaysOfCode. &lt;/p&gt;

&lt;p&gt;I used the &lt;a href="https://developer.twitter.com/en/docs/twitter-api/v1/tweets/search/api-reference/get-search-tweets"&gt;Twitter search tweets API&lt;/a&gt; to get the tweets of a user containing a specific hashtag and in a given date range.&lt;/p&gt;

&lt;p&gt;This is how the request looks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.twitter.com/1.1/search/tweets.json?q=from%3Asdinesh91%20since%3A2021-12-24%20until%3A2021-12-25%20#100DaysOfCode%20-filter%3Aretweets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the query parameters I used&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from:sdinesh91 since:2021-12-24 until:2021-12-25 #100DaysOfCode -filter:retweets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query returns tweets of a specific user, tweeted within a specific date range and having the specified hashtag. We also don't need to fetch the retweets, so I filter them out.&lt;/p&gt;

&lt;p&gt;We don't have to count multiple tweets containing the hastag in a single day. 100DaysOfCode is progress made in days and not in the number of times tweeted.&lt;/p&gt;

&lt;p&gt;Having fetched the tweets it is very simple to count the progress. If I find a tweet for any given day, I increment the count. Once I have the count, I update the progress in the database and push the new progress bar to Twitter.&lt;/p&gt;

&lt;p&gt;One important aspect of writing a backend job like this, is to make it &lt;strong&gt;Idempotent&lt;/strong&gt;. Being idempotent, ensures that we obtain the same result irrespective of the number of times we run the code. This is critical as backend jobs might fail or timeout. In such cases, we should be able to just rerun the job and things should work as expected.&lt;/p&gt;

&lt;p&gt;To make tracker job idempotent, I store the tracker's last run date to each user's config. This allows the tracker to resume processing from where it last left by comparing the last run date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech
&lt;/h3&gt;

&lt;p&gt;I have hosted the tracker on &lt;a href="//netlify.com"&gt;Netlify&lt;/a&gt; as a function. I also use &lt;a href="https://www.easycron.com/"&gt;EasyCron&lt;/a&gt; to trigger the tracker function every day.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>sideprojects</category>
      <category>webdev</category>
      <category>twitter</category>
    </item>
    <item>
      <title>How I built SetProgress - Part 2 Progress bar</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Fri, 17 Dec 2021 03:06:12 +0000</pubDate>
      <link>https://dev.to/dineshs91/how-i-built-setprogress-part-2-progress-bar-3ehj</link>
      <guid>https://dev.to/dineshs91/how-i-built-setprogress-part-2-progress-bar-3ehj</guid>
      <description>&lt;p&gt;This is the 2nd post in the series "How I built SetProgress".&lt;/p&gt;

&lt;p&gt;Before we dive in, let me explain the features of &lt;a href="https://setprogress.co/"&gt;SetProgress&lt;/a&gt;.&lt;br&gt;
SetProgress allows you to display your progress to your Twitter Audience. If you are an active user on Twitter you might have noticed accounts with a progress bar in their profile. The most common one's are &lt;strong&gt;MRR&lt;/strong&gt; (Monthly recurring revenue), &lt;strong&gt;#100DaysOfCode&lt;/strong&gt; and many more. &lt;/p&gt;

&lt;p&gt;Progress bar examples&lt;br&gt;
  &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GE-6RQom--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvrzj6a3igxx0ysv0fpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GE-6RQom--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvrzj6a3igxx0ysv0fpp.png" width="854" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before I built this app, I saw a lot of people on Twitter sharing their progress using progress bars in their bio. I asked a few of them how they were doing it and they said, they were creating those blocks manually. That works fine if your progress doesn't change frequently. But if you are doing &lt;strong&gt;#100DaysOfCode&lt;/strong&gt; or something similar, progress changes quite frequently. It would also be cumbersome for a user to calculate the number of bars in the progress bar.&lt;/p&gt;

&lt;p&gt;Let's say you are doing &lt;code&gt;100DaysOfCode&lt;/code&gt; and your have completed 56 days, what would the progress bar look like? How many blocks do you want to have in the bar and how do you fill them with full/partial blocks? There is a bit of mathematic calculation here. In 2021, nobody should be doing this sort of thing manually.&lt;/p&gt;

&lt;p&gt;I decided to build an app to automate this. There are 2 things to be solved here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Create the progress bar based on the current progress, target progress and the number of bars in the progress bar. (As configured by the user in the app)&lt;/li&gt;
&lt;li&gt;Push the progress bar to Twitter.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;SetProgress Architecture&lt;br&gt;
  &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OxAwcG-w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xphr7t5hu6xwjw6uapl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OxAwcG-w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xphr7t5hu6xwjw6uapl1.png" width="552" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use the app, user has to login using Twitter. User profile information is fetched from Twitter and displayed in the app. Signing in with Twitter also enables the app to push the progress bar to Twitter. &lt;/p&gt;

&lt;h3&gt;
  
  
  Progress bar design
&lt;/h3&gt;

&lt;p&gt;The first step in designing a progress bar is to configure it. It is made of blocks which display the progress. There are 3 types of blocks&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Empty&lt;/li&gt;
&lt;li&gt;Partial&lt;/li&gt;
&lt;li&gt;Full&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The blocks can be made of any emoji. For convenience, I have collected commonly used one's into a dropdown, which can be selected while configuring.&lt;/p&gt;

&lt;p&gt;The bar is calculated based on the current and the target progress. Just a bare progress bar doesn't convey much meaning. So you also configure what should be displayed to its left and right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save to DB
&lt;/h3&gt;

&lt;p&gt;Users can configure the progress and save it to the database. By doing this, they don't have to configure the progress every time they come to the app. If you think about it, once you do the initial configuration, you would just update the current progress, everything else remains the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push to Twitter
&lt;/h3&gt;

&lt;p&gt;Once the user Sign's In with Twitter, the app receives the user's access token which is required for updating the user profile &lt;a href="https://developer.twitter.com/en/docs/twitter-ads-api/making-authenticated-requests"&gt;Twitter doc&lt;/a&gt;. Progress bar is nothing but a plain string. When user clicks on &lt;code&gt;Push to Twitter&lt;/code&gt;, an API call is made to Twitter to update their profile. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tech stack
&lt;/h3&gt;

&lt;p&gt;I am using Next.js for backend along with FaunaDB as the datastore. &lt;/p&gt;

&lt;p&gt;I am using ReactJS and Tailwindcss for frontend. &lt;/p&gt;

&lt;p&gt;The app is hosted on Vercel. It has a mix of Server Side rendered pages and API's. You don't want to expose API keys and database secrets in the client. So I proxy the requests through API's that require Twitter tokens (For pushing bar to Twitter) and database credentials (For storing the progress bar configuration). &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>twitter</category>
      <category>webdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>7 tips to help you deploy code with confidence</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Fri, 10 Dec 2021 03:44:47 +0000</pubDate>
      <link>https://dev.to/dineshs91/7-tips-to-deploy-with-confidence-3lgo</link>
      <guid>https://dev.to/dineshs91/7-tips-to-deploy-with-confidence-3lgo</guid>
      <description>&lt;p&gt;Writing code is great, but how do you deploy your code with confidence?&lt;/p&gt;

&lt;p&gt;Here are 7 tips that will help you with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Code reviews&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get your code reviewed by your peers. Common patterns and bugs can be spotted in these reviews by senior devs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Deploy small features/chunks of code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deploying a small feature set reduces the number of errors that might come up. This also restricts issues to the feature being deployed, which makes it easy to debug and test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Keep an eye out for database migration changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need to be careful when deploying database migrations. Identify if they are destructive or not and employ migration deployment strategies accordingly. Deployment with a migration change makes it challenging to do rollbacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Write unit tests &amp;amp; integrate them with CI system&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Having unit tests validates the requirement and makes sure any new code changes don't break existing functionality. Make sure your CI system runs these on all PR's to validate that the new changes don't break the existing code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Testing in test/gamma environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can have automated integration tests to verify if the code works when integrated with other services. Also manually testing in Gamma/Staging env is always better as these environments mimic production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Have a rollback strategy ready&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before you deploy your code, have a rollback plan ready. Let's say there is a bug after deployment, how do you roll back to the previous version? There won't be time to think when production is broken. This will save you from panic &amp;amp; distress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Have a good error-monitoring system in place.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't wait for users to notify you of any issues in your application. Have an error-monitoring system which allows you to respond to issues promptly &amp;amp; proactively.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I built SetProgress - Part 1 Waitlist</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Wed, 08 Dec 2021 11:42:00 +0000</pubDate>
      <link>https://dev.to/dineshs91/how-i-built-setprogress-part-1-waitlist-1hja</link>
      <guid>https://dev.to/dineshs91/how-i-built-setprogress-part-1-waitlist-1hja</guid>
      <description>&lt;p&gt;This is going to be a multi part series. In this post, I am going to explain how I created a waitlist page for my app &lt;a href="https://setprogress.co/" rel="noopener noreferrer"&gt;SetProgress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a simple waitlist, you require only 2 things, a landing page and a way to collect &amp;amp; store emails or phone numbers. &lt;/p&gt;

&lt;p&gt;Waitlist Architecture&lt;br&gt;
  &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lu3vfdehn6of3o5ixj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lu3vfdehn6of3o5ixj7.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Landing page
&lt;/h3&gt;

&lt;p&gt;For the landing page, I used &lt;a href="https://unicornplatform.com/" rel="noopener noreferrer"&gt;Unicorn platform&lt;/a&gt;. I chose an existing template and made some minor tweaks to it. I did not want to spend time building a page myself since waitlist is a temporary thing and I could spend that time on building the app itself. &lt;/p&gt;

&lt;p&gt;I took their &lt;code&gt;Maker&lt;/code&gt; pricing plan, so that I can export the code and host it myself. I did not want to depend on another platform for hosting and keep paying them just for that. &lt;/p&gt;

&lt;h3&gt;
  
  
  Collecting emails
&lt;/h3&gt;

&lt;p&gt;I wrote a few lines of JavaScript code which will make a call to an API which will have the email the user entered in the waitlist input box. On success, I show the user an alert message with the text &lt;code&gt;Added to waitlist successfully&lt;/code&gt;. I just used javascript &lt;code&gt;alert(success)&lt;/code&gt; for it, no fancy pop up's.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing waitlist emails
&lt;/h3&gt;

&lt;p&gt;I decided to store the emails in Airtable. I created an automation in Integromat which will receive the emails through a webhook and write them to Airtable. The automation is a Webhook to Airtable integration. The ajax call from the waitlist form is made to the Webhook API url provided by Integromat. &lt;/p&gt;

&lt;p&gt;Integromat integration&lt;br&gt;
  &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0r1moxq1js3l9wmyz8gu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0r1moxq1js3l9wmyz8gu.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the logo
&lt;/h3&gt;

&lt;p&gt;I created a simple logo from Figma.&lt;/p&gt;

&lt;p&gt;App logo&lt;br&gt;
  &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fh0nndhyuvg8ul2ju06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fh0nndhyuvg8ul2ju06.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created it using emoji's 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools used for the waitlist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Unicorn platform, for building the landing page.&lt;/li&gt;
&lt;li&gt;Figma, for creating the logo.&lt;/li&gt;
&lt;li&gt;Storyset, for illustration. I used &lt;a href="https://storyset.com/illustration/fast-loading/bro" rel="noopener noreferrer"&gt;Fast-loading&lt;/a&gt; illustration.&lt;/li&gt;
&lt;li&gt;Airtable, to store the emails&lt;/li&gt;
&lt;li&gt;Integromat, to create a webhook and write data to Airtable&lt;/li&gt;
&lt;li&gt;Vercel to deploy the waitlist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used the free tier of all the above tools, except Unicorn platform for which I did one time payment of 8$ for a month, just to export the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;As you can see, a waitlist doesn't have to be complicated. It can be done in a few simple steps. I don't recommend anyone to spend a lot of time on the waitlist. Focus your time &amp;amp; energy on the actual app itself.&lt;/p&gt;

&lt;p&gt;Here's how the waitlist page looks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fio8973bisom9nmp5x9mi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fio8973bisom9nmp5x9mi.png"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>twitter</category>
      <category>webdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Build an app using Next.js and Twitter API</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Fri, 03 Dec 2021 06:46:16 +0000</pubDate>
      <link>https://dev.to/dineshs91/build-an-app-using-nextjs-and-twitter-api-2ebp</link>
      <guid>https://dev.to/dineshs91/build-an-app-using-nextjs-and-twitter-api-2ebp</guid>
      <description>&lt;p&gt;In this post I am going to walk you through the Next.js Twitter starter template I have created. Using this guide, you can build your own app using Next.js and Twitter API.&lt;/p&gt;

&lt;p&gt;Let's get started. &lt;/p&gt;

&lt;h3&gt;
  
  
  Clone the starter kit
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Dineshs91" rel="noopener noreferrer"&gt;
        Dineshs91
      &lt;/a&gt; / &lt;a href="https://github.com/Dineshs91/nextjs-twitter-starter" rel="noopener noreferrer"&gt;
        nextjs-twitter-starter
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A starter kit for building apps using Twitter API with Next.js
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;This is a Next.js Twitter starter kit.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Packages&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/docs" rel="nofollow noopener noreferrer"&gt;Tailwindcss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/draftbit/twitter-lite" rel="noopener noreferrer"&gt;Twitter-lite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Create &lt;code&gt;.env.local&lt;/code&gt; file in the project root and add the following content in it&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_KEY_SECRET=
TWITTER_BEARER_TOKEN=
TEST_TWITTER_HANDLE=
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To get the Twitter keys, visit &lt;a href="https://developer.twitter.com/en/portal/dashboard" rel="nofollow noopener noreferrer"&gt;https://developer.twitter.com/en/portal/dashboard&lt;/a&gt; and create a standalone app. Fetch the consumer key, secret and bearer token and add it to the &lt;code&gt;.env.local&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Add your twitter handle for &lt;code&gt;TEST_TWITTER_HANDLE&lt;/code&gt;. This is used in the twitter sample page.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;App vs. User authentication&lt;/h4&gt;
&lt;/div&gt;
&lt;p&gt;Twitter has two different authentication options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;App: higher rate limits. Great for building your own Twitter App.&lt;/li&gt;
&lt;li&gt;User: lower rate limits. Great for making requests on behalf of a User.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;User&lt;/strong&gt; authentication requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;consumer_key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;consumer_secret&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;access_token_key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;access_token_secret&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt; authentication requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bearer_token&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the example in this starter, we use App authentication which makes use of the &lt;code&gt;bearer_token&lt;/code&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Install dependencies&lt;/h4&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Run the development server:&lt;/h4&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn dev&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; with your browser to…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Dineshs91/nextjs-twitter-starter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;First, clone the repository to your local computer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone https://github.com/Dineshs91/nextjs-twitter-starter.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This kit contains the following packages for you to get started.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Twitter-lite&lt;/li&gt;
&lt;li&gt;Tailwindcss&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install dependencies
&lt;/h3&gt;

&lt;p&gt;cd into the newly cloned repository and run the following command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note: If you don't have &lt;code&gt;yarn&lt;/code&gt; installed, install it by following the instructions provided here &lt;a href="https://classic.yarnpkg.com/lang/en/docs/install" rel="noopener noreferrer"&gt;https://classic.yarnpkg.com/lang/en/docs/install&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up local configuration
&lt;/h3&gt;

&lt;p&gt;Create a file to store the local environment variables.&lt;/p&gt;

&lt;p&gt;Create a file in the root directory with the name &lt;code&gt;.env.local&lt;/code&gt; and add the following in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_KEY_SECRET=
TWITTER_BEARER_TOKEN=
TEST_TWITTER_HANDLE=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get the values of &lt;code&gt;TWITTER_CONSUMER_KEY&lt;/code&gt;, &lt;code&gt;TWITTER_CONSUMER_KEY_SECRET&lt;/code&gt; &amp;amp; &lt;code&gt;TWITTER_BEARER_TOKEN&lt;/code&gt; from Twitter developer portal. &lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="https://developer.twitter.com/en/portal/dashboard" rel="noopener noreferrer"&gt;https://developer.twitter.com/en/portal/dashboard&lt;/a&gt; and create a new standalone app. Once you create the app, you will get the required values which you can add in the &lt;code&gt;.env.local&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TEST_TWITTER_HANDLE&lt;/code&gt; can be any Twitter handle. This key is used in the demo page to fetch their public profile information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local development
&lt;/h3&gt;

&lt;p&gt;To start your local development server, run the following command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once the server starts, navigate to &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Checkout the demo page at  &lt;a href="http://localhost:3000/twitter" rel="noopener noreferrer"&gt;http://localhost:3000/twitter&lt;/a&gt;. The information you see there, is pulled from Twitter using their official API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Develop
&lt;/h3&gt;

&lt;p&gt;The starter kit contains one example for both SSR and API.&lt;/p&gt;

&lt;p&gt;It has one server side rendered (SSR) page and one api &lt;code&gt;api/twitter-user&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSR page &lt;code&gt;pages/index.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;API &lt;code&gt;pages/api/twitter-user.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can start editing the page by modifying &lt;code&gt;pages/index.js&lt;/code&gt;. The page auto-updates as you edit the file.&lt;/p&gt;

&lt;p&gt;Test the API from postman, by sending a post request with a request body in the below format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "twitter_handle": "SDinesh91"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response will be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "screen_name": "SDinesh91",
    "name": "Dinesh",
    "profile_image_url": "https://pbs.twimg.com/profile_images/1421346630456922112/fVyiui9f_normal.jpg"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the header &lt;code&gt;Content-Type&lt;/code&gt; is set to &lt;code&gt;application/json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, go ahead and try it out with your own twitter handle.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pages/api&lt;/code&gt; directory is mapped to &lt;code&gt;/api/*&lt;/code&gt;. Files in this directory are treated as API routes instead of React pages.&lt;/p&gt;

&lt;p&gt;Checkout the example located at &lt;code&gt;pages/twitter.js&lt;/code&gt;. You can navigate to &lt;a href="http://localhost:3000/twitter" rel="noopener noreferrer"&gt;http://localhost:3000/twitter&lt;/a&gt; in your browser to see it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hacking
&lt;/h3&gt;

&lt;p&gt;That's it. You are all set to build your own app using Twitter API. Share your app with me on &lt;a href="https://twitter.com/SDinesh91" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;The easiest way to deploy your app is to use the &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; Platform from the creators of Next.js.&lt;/p&gt;

&lt;p&gt;Check out Next.js &lt;a href="https://nextjs.org/docs/deployment" rel="noopener noreferrer"&gt;deployment documentation&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;If you would like to make requests on behalf of another user, you will need to generate a separate set of Access Tokens for that user using the 3-legged OAuth flow, and pass that user's tokens with your OAuth 1.0a User Context requests.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://next-auth.js.org/getting-started/introduction" rel="noopener noreferrer"&gt;next-auth.js&lt;/a&gt; for OAuth. This package is not included in the starter, you will have to add it manually.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Django Soft delete options</title>
      <dc:creator>Dinesh S</dc:creator>
      <pubDate>Mon, 29 Nov 2021 09:03:03 +0000</pubDate>
      <link>https://dev.to/dineshs91/django-soft-delete-options-2g13</link>
      <guid>https://dev.to/dineshs91/django-soft-delete-options-2g13</guid>
      <description>&lt;p&gt;In this post, I try and explore different ways of doing soft delete's in Django, either using a library or from the models directly.&lt;/p&gt;

&lt;p&gt;The reason I am trying out all these options, is to make sure that the consequences of any framework or approach, are well known before making a choice. And accidental deletion of production data is no joke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approaches:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paranoia model&lt;/li&gt;
&lt;li&gt;Django safe delete &lt;a href="https://github.com/makinacorpus/django-safedelete"&gt;https://github.com/makinacorpus/django-safedelete&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For all the approaches, we will verify how the following cases are handled.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;Queryset GET and DELETE&lt;/li&gt;
&lt;li&gt;Relations&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Paranoia model:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I found this code in &lt;a href="https://github.com/getsentry/sentry"&gt;sentry&lt;/a&gt;&lt;br&gt;
The idea here is to create a custom model manager, which includes a custom queryset.&lt;br&gt;
We create &lt;code&gt;ParanoiaModel&lt;/code&gt;, which will serve as the base model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParanoidModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;abstract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;deleted_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any model which needs safe delete, can inherit this base model. This works well, only when an individual object has to be deleted. But would fail, when delete is issued on a queryset.&lt;/p&gt;

&lt;p&gt;So we add a custom queryset &lt;code&gt;ParanoidQuerySet&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParanoidQuerySet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QuerySet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Prevents objects from being hard-deleted. Instead, sets the
    ``date_deleted``, effectively soft-deleting the object.
    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParanoidManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Only exposes objects that have NOT been soft-deleted.
    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ParanoidQuerySet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;deleted_on__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParanoidModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;abstract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;deleted_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ParanoidManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;original_objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also add a custom manager. It helps us in 2 ways. We can access the original manager, which will return the soft deleted objects and second, queries that return queryset will filter the soft deleted objects without the need for us to specify it in each query.&lt;/p&gt;

&lt;p&gt;Now delete's work on both individual objects and querysets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ParanoidModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"soft delete strategies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Trying out various soft delete strategies"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will soft delete the post
&lt;/span&gt;
&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will also soft delete all the posts.
&lt;/span&gt;
&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will not return any post and will raise an exception.
&lt;/span&gt;
&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;original_objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will return the soft deleted post.
&lt;/span&gt;
&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;original_objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Returns soft deleted objects as well, along with the undeleted ones.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This strategy works very well for the first 3 criterion. But how does this work across relations ?&lt;/p&gt;

&lt;p&gt;Let's add another model to the above example, to understand how delete's work across relations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"soft delete strategies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Trying out various soft delete strategies"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ParanoidModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Well written blog post"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# post is the object we created earlier.
&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Soft delete the post.
&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# The comment of the post still exists.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the above example, it is clear that the soft delete is not propagated to the relations. Deleting a post, does not delete the comments related to it. They still can be accessed independently, but cannot be accessed from the post, since the post is soft deleted.&lt;/p&gt;

&lt;p&gt;So summarising this approach, everything works well, other than the relations handling. This implementation is good enough, if the relation models are not queried directly. For example, once we delete the post, the comments related to the post become irrelevant. Comments don't mean a thing without their parent &lt;code&gt;post&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other thing to note here is, if we decide to restore a soft deleted object, we don't have to worry about its relations, since they have not been deleted (Neither soft/hard).&lt;br&gt;
If you want restore option, you can add &lt;code&gt;undelete&lt;/code&gt; method to the base model &lt;code&gt;ParanoiaModel&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParanoidModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;abstract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;deleted_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ParanoidManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;original_objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;undelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also add this to the custom queryset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParanoidQuerySet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QuerySet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Prevents objects from being hard-deleted. Instead, sets the
    ``date_deleted``, effectively soft-deleting the object.
    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;undelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I've made some changes to the code found from sentry, like changing the field name &lt;code&gt;deleted_on&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Django safe delete&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This framework provides lot of options for soft deleting. They have the following policies&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HARD_DELETE&lt;/li&gt;
&lt;li&gt;SOFT_DELETE&lt;/li&gt;
&lt;li&gt;SOFT_DELETE_CASCADE&lt;/li&gt;
&lt;li&gt;HARD_DELETE_NOCASCADE&lt;/li&gt;
&lt;li&gt;NO_DELETE&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Policies apply to how the delete is handled and stored in the database.&lt;/p&gt;

&lt;p&gt;They have the following visibility options&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DELETED_INVISIBLE (Default)&lt;/li&gt;
&lt;li&gt;DELETED_VISIBLE_BY_FIELD&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Visibility options apply for retrieving data.&lt;/p&gt;

&lt;p&gt;I will focus only on the soft delete policies, as other options are irrelevant for this post. You can check out their &lt;a href="https://django-safedelete.readthedocs.io/en/latest/index.html"&gt;documentation&lt;/a&gt; if you are interested&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HARD_DELETE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is similar to the Django default behaviour, with some more options. I am not going to discuss them here. You can checkout their documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SOFT_DELETE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This policy just soft deletes the object being deleted. The related objects remain untouched.&lt;/p&gt;

&lt;p&gt;Let's start by creating some models&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;safedelete.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SafeDeleteModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;safedelete.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SOFT_DELETE&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SafeDeleteModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_safedelete_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SOFT_DELETE&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SafeDeleteModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_safedelete_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SOFT_DELETE&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'article_comments'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will try deleting an article&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# First we create an article
&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"article 1 title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"article 1 content"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will soft delete the article.
&lt;/span&gt;
&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will soft delete all the articles.
&lt;/span&gt;
&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will return objects which are not deleted (Either soft/hard)
&lt;/span&gt;
&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_with_deleted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will fetch all the objects including the deleted one's
&lt;/span&gt;
&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;original_objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Will fetch all the objects including the deleted one's using our custom manager.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can restore the soft deleted object with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;article.undelete()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this approach, first 3 criterion work well, but fails for relations. So soft deleting an object, doesn't soft delete its relations.&lt;/p&gt;

&lt;p&gt;This strategy is almost similar to the paranoia design we discussed above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SOFT_DELETE_CASCADE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is almost similar to the above, except that it soft delete's the related objects as well.&lt;/p&gt;

&lt;p&gt;Let's start by creating some models&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;safedelete.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SafeDeleteModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;safedelete.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SOFT_DELETE_CASCADE&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SafeDeleteModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_safedelete_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SOFT_DELETE_CASCADE&lt;/span&gt;
    &lt;span class="n"&gt;full_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SafeDeleteModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_safedelete_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SOFT_DELETE_CASCADE&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"user_logins"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;login_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will try deleting the user&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sam kin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sam@gm.com"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;UserLogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;UserLogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# User count will be 0
&lt;/span&gt;
&lt;span class="n"&gt;UserLogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# UserLogin count will also be 0. (Since this is cascade soft delete)
# Both user and user login are soft deleted.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that soft delete's are propagated to the relations as well.&lt;/p&gt;

&lt;p&gt;Here, restoring user object will restore all of its login's. So all the related objects are restored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.undelete()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach handles all our criterions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NO_DELETE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This policy prevents any sort of delete soft/hard. The only way to delete, is through raw sql query. This can be useful in places, where any kind of delete is not allowed, from the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;All the approaches fall under 2 categories&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Supports relations&lt;/li&gt;
&lt;li&gt;Doesn't support relations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want your soft-delete's, to be propagated to the relations use soft-delete-cascade. If this is not required, then you can choose any of the above approaches.&lt;/p&gt;

&lt;p&gt;You can find the code samples with tests in my Github repo &lt;a href="https://github.com/Dineshs91/soft-delete-options-in-django/"&gt;soft-delete-options-in-django&lt;/a&gt;&lt;/p&gt;

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