Posted in Python, Tech

Twitter Clone with Django REST Framework – II

Previous post: Twitter Clone with Django REST Framework – I

User Authentication

We will perform Authentication using JWT or JSON Web Token. JWT is a secret token that is generated and sent to the browser when a user successfully logs in with his credentials. Subsequently, whenever the user accesses any link, the token is sent back to the server in the request header. The server checks for the validity of the token before granting access.

User ————-> Login Successful ——————————–> Server: Generates JWT

User <————- Returns JWT to browser ——————– Server

User ————-> Sends JWT in request header ————> Server validates JWT signature

User <————- Access Granted ——————————– Server sends Response

We will be using Python’s JWT Module – PyJWT here. I did try out Django Rest Framework’s JWT, but I found it hard to get it working with little/unclear documentation. Also, the process of token generation and encryption is better understandable using PyJWT. It is also very flexible for customization. To install:

pip install PyJWT

settings.py

#In settings.py
import uuid

#RANDOM AUTHENTICATION TOKEN GENERATOR
AUTH_TOKEN = uuid.uuid4().hex.upper()

Let’s write the views for Authentication:

import jwt

#set Expiration time for the token. Best practise is to set it less than 10 minutes
EXP_TIME = datetime.timedelta(minutes=5)

def _get_token(request=None):
    return request.META.get('HTTP_AUTHORIZATION') or request.query_params['token']

@api_view(['POST'])
def GetToken(request):
    user = request.query_params['username']
    pwd = request.query_params['password']
    try:
        user = TUser.objects.get(username=user,password=pwd)
        if user:
            try:
                payload = {'id':user.id,'username':user.username,'exp':datetime.datetime.utcnow()+EXP_TIME}
                token = {'token':jwt.encode(payload,settings.AUTH_TOKEN)}
                return Response(token, status=status.HTTP_200_OK)
            except Exception as e:
                raise e
        else:
            resp = {'Authentication Failed. Wrong Username/Password'}
            return Response(resp, status=status.HTTP_403_FORBIDDEN)
    except:
        return Response("Please provide a valid Username and Password",status=status.HTTP_400_BAD_REQUEST)   


@api_view(['POST'])
def Login(request):
    token = _get_token(request)
    try:
        payload = jwt.decode(token,settings.AUTH_TOKEN)
        user = TUser.objects.get(username = payload.get('username'))
        serializer = TUserSerializer(user)
    except (jwt.ExpiredSignature, jwt.DecodeError, jwt.InvalidTokenError):
        return Response("Error: Token is Invalid", status=status.HTTP_403_FORBIDDEN)
    except:
        return Response("Error: Internal Server Error", status=status.HTTP_403_FORBIDDEN)
    return Response(serializer.data, status=status.HTTP_201_CREATED)

App User – REST APIs

Here’s the API for the User App:

urls.py

from django.urls import path
from . import views
from rest_framework import routers, serializers, viewsets

urlpatterns = [
    path('signup', views.AccountSignup),
    path('users',views.users),
    path('<str:username>/account', views.AccountUpdate),
    path('auth', views.GetToken),
    path('login', views.Login),
    path('<str:loggedin_user>/<str:user>/follow', views.FollowUser),
    path('<str:username>/followers', views.GetFollowers),
    path('<str:username>/following', views.GetFollowing),
    path('<str:username>/block', views.Block_user), 
]

serializer.py

from rest_framework import serializers
from TUsers.models import TUser

class TUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = TUser
        fields = '__all__'

views.py

from django.shortcuts import render
from django.conf.urls import url
from TUsers.models import TUser
from django.shortcuts import render
from django.http import HttpResponse,QueryDict
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from TUsers.serializer import TUserSerializer
import json
import logging
import datetime
from django.conf import settings
import jwt

EXP_TIME = datetime.timedelta(minutes=5)

def _get_token(request=None):
    return request.META.get('HTTP_AUTHORIZATION') or request.query_params['token']

@api_view(['POST'])
def GetToken(request):
    user = request.query_params['username']
    pwd = request.query_params['password']
    try:
        user = TUser.objects.get(username=user,password=pwd)
        if user:
            try:
                payload = {'id':user.id,'username':user.username,'exp':datetime.datetime.utcnow()+EXP_TIME}
                token = {'token':jwt.encode(payload,settings.AUTH_TOKEN)}
                return Response(token, status=status.HTTP_200_OK)
            except Exception as e:
                raise e
        else:
            resp = {'Authentication Failed. Wrong Username/Password'}
            return Response(resp, status=status.HTTP_403_FORBIDDEN)
    except:
        return Response("Please provide a valid Username and Password",status=status.HTTP_400_BAD_REQUEST)   

@api_view(['POST'])
def Login(request):
    token = _get_token(request)
    try:
        payload = jwt.decode(token,settings.AUTH_TOKEN)
        user = TUser.objects.get(username = payload.get('username'))
        serializer = TUserSerializer(user)
    except (jwt.ExpiredSignature, jwt.DecodeError, jwt.InvalidTokenError):
        return Response("Error: Token is Invalid", status=status.HTTP_403_FORBIDDEN)
    except:
        return Response("Error: Internal Server Error", status=status.HTTP_403_FORBIDDEN)
    return Response(serializer.data, status=status.HTTP_201_CREATED)
    

@api_view(['POST'])
def AccountSignup(request):
    serializer = TUserSerializer(data=request.query_params)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.error, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET'])
def users(request):
    if request.method == 'GET':
        users = TUser.objects.all()
        serializer = TUserSerializer(users, many=True)
        return Response(serializer.data)
        
@api_view(['PUT'])
def AccountUpdate(request,username):
    try:
        query_dict = QueryDict('', mutable=True)
        query_dict.update({"username": username})
        query_dict.update(request.query_params)
        serializer = TUserSerializer(data=query_dict)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.error, status=status.HTTP_400_BAD_REQUEST)
    except:
        return Response("Invalid details", status=status.HTTP_400_BAD_REQUEST)

@api_view(['PUT'])
def FollowUser(request,loggedin_user,user):
    try:
        cur_user=TUser.objects.get(username=loggedin_user)
        fol_user=TUser.objects.get(username=user)
        cur_user.following.add(fol_user)
        cur_user.save()
        return Response("Follow Request Updated", status=status.HTTP_204_NO_CONTENT)
    except:
        return Response("Request Failed. Invalid Details", status=status.HTTP_400_BAD_REQUEST)    

@api_view(['GET'])
def GetFollowers(request,username):
    try:
        user = TUser.objects.get(username=username)
        followers = user.followers.all()
        serializer = TUserSerializer(followers, many=True)
        return Response(serializer.data)
    except:
        return Response("User does not exist!", status=status.HTTP_400_BAD_REQUEST)  

@api_view(['GET'])
def GetFollowing(request,username):
    try:
        user = TUser.objects.get(username=username)
        following = user.following.all()
        serializer = TUserSerializer(following, many=True)
        return Response(serializer.data)
    except:
        return Response("User does not exist!", status=status.HTTP_400_BAD_REQUEST)  

@api_view(['PUT'])
def Block_user(request,username):
    try:
        user = TUser.objects.get(username=username)
        user.blocked = True
        user.save()
        return Response("User Blocked", status=status.HTTP_204_NO_CONTENT)
    except:
        return Response("User Does not exist!", status=status.HTTP_400_BAD_REQUEST) 

The entire code can be found in my GitHub repository: https://github.com/shilpavijay/TwitterClone

Posted in Python, Tech

Twitter Clone with Django REST Framework – I

In this blogpost and the forthcoming ones, I will be building a Twitter Clone using Django REST framework.

  • There will be a code snippet for each step. You can find the same on GitHub too: https://github.com/shilpavijay/TwitterClone
  • The approach I have taken is TDD – Test Driven Development. There will be a test case written for each functionality, followed by development.

Setting up the Django Environment

conda create env --name django_env
conda activate django_env

For Django REST Framework:

pip install djangorestframework

# Add to settings.py:
INSTALLED_APPS = [
     ...
    'rest_framework',
]

Let’s begin the Django Project

#To start a Django Project:
django-admin startproject TwitterClone

#Start the Apps:
python manage.py startapp TUsers
python manage.py startapp Tweets

#Add the two Apps to settings.py
INSTALLED_APPS = [
     ...
    'Tweets',
    'TUsers',
]

Writing Test Cases:

  • Create a tests folder inside the App as shown below.
  • This has to be made into a package by creating a ‘__init__.py’.
  • All the files in the package should begin with a test_<>
  • We will be writing test cases that initially fail and then coding the functionality, with the final goal of passing the test cases.

test_users.py and test_tweets.py

from django.test import TestCase
from TUsers.models import TUser

class TUserTestCase(TestCase):
    def setUp(self):      
        TUser.objects.create(username="mark_cuban",
                             password="sharktank",
                             country="USA")
    def test_create(self):
        self.assertEqual("USA",
                      TUser.objects.get(username="mark_cuban").country)
from django.test import TestCase
from Tweets.models import TCtweets
from TUsers.models import TUser

class TweetsTestCase(TestCase):
    def setUp(self):
        user1 = TUser.objects.create(username="lori",password="sharktank",country="USA")
        TCtweets.objects.create(username=user1,tweet_text="Hi there!")
    def test_create_tweet(self):
        user1 = TUser.objects.get(username='lori')
        self.assertEqual("Hi there!",TCtweets.objects.get(username=user1).tweet_text)

Adding the models:

from django.db import models
from Tweets.views import get_cur_time

class TUser(models.Model):
    username = models.CharField(max_length=100,unique=True)
    password = models.CharField(max_length=100)
    country = models.CharField(max_length=100,null=True)
    modified_time = models.CharField(max_length=50,default=get_cur_time())
    following = models.ManyToManyField('self',related_name='followers',symmetrical=False,blank=True)
    blocked = models.BooleanField(default=False)
from django.db import models
from Tweets.views import get_cur_time
from TUsers.models import TUser

class TCtweets(models.Model):
    username = models.ForeignKey(TUser,on_delete=models.CASCADE)
    tweet_text = models.CharField(max_length=100,null=True)
    time = models.CharField(max_length=50,default=get_cur_time())
    retweet = models.ManyToManyField("self")
    like = models.IntegerField(null=True)
    comment = models.ManyToManyField("self")

Swagger

Let us set up Swagger for API documentation:

pip install django-rest-swagger

#Add to settings.py
INSTALLED_APPS = [
     ...
    'rest_framework_swagger',
]

#Add the following to urls.py
from rest_framework_swagger.views import get_swagger_view

schema_view = get_swagger_view(title='Twitter Clone API')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('docs/',schema_view),
]

If you are using Django 3.x, (which is my case here) there are a few things to be done in order to make Swagger work:

  • Enable CoreAPI Schema: Django Rest Framework 3.x deprecated the CoreAPI based schema generation and introduced the OpenAPI schema generation in its place. Currently to continue to use django-rest-swagger we need to re-enable the CoreAPI schema. To settings.py add:
REST_FRAMEWORK = 
{'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' }
  • Change the swagger index.file: {% load staticfiles %} has been deprecated in django 2.x and removed in 3.x. Modify the same in index.html file of swagger in the location: …/Anaconda\envs\dj\Lib\site-packages\rest_framework_swagger\templates\rest_framework_swagger
...
{% load static %}  #Remove {% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
...
...

And finally, you are done. You should see the Swagger API page.

Test Case for APIs

Too test the login API, first let’s write the test case and then build the code for the same. Here’s a sample:

test_users.py

   def test_api_login(self):
        client = Client()
        response = client.post('/login/', 
                   {'username':'mark_cuban'},format='json')
                    self.assertEqual(200,response.status_code)

In the next blogpost, we will see how to build the REST APIs.

Posted in <3 for books

The Hobbit – J.R.R. Tolkien

In a hole in the ground there lived a hobbit…

Words fall short to describe this book. Some books are a joy to read any number of times and are made for all age groups. I can’t believe this book was actually written by the author for his kids.

The Map in the book (find one below) adds to the enthusiasm to read about Bilbo’s adventure, that starts from his hobbit hole and ends with his return back home.

For those who have not read the book or watched the movie, Bilbo Baggins is a hobbit who lives in a cozy hobbit hole filled with lots (and lots!) of food. He is lured into going on an adventure by a wizard named Gandalf. He accompanies the 13 Dwarfs to capture the kingdom under the Lonely Mountain which once belonged to the dwarfs and is now a home to a dragon. And so the adventure begins…

As someone rightly said, it is not about the Destination but about the Journey. It is the same with this adventure. They pass through a lot of hurdles and on their way make many friends and foes. Finally, Bilbo enjoys his return to his cozy home much more than before he started the adventure.

The Movie is also pretty good. I watched the movie first before reading the book. The movie has brought in a lot of characters that do not exist in the book at all. That is the only way, I think, they could make three movies out of a 300 pages book. But, I personally like the book more that the movies. When you read, you actually image your own universe and characters unlike movies which forces someone else’s imagination on you.

The Hobbit is a Classic! I look forward to read The Lord of the Rings as well.

Posted in Cooking is Fun!

Happiness is… the smell of freshly baked Bread!

Here’s what you will need to make a soft Wheat bread at home:

Wheat Flour – 3 cups

Butter – 2 cubes

Milk – 1 cup

Sugar – 15 grams

Dry yeast – 6 grams

Salt – 1 teaspoon

Oil – 1 tablespoon

Step 1: Activating the Yeast

Add 2 cubes of melted butter, 1 cup of warm milk, 15 grams or approximately 1 tablespoon of sugar and 6 grams or 1/2 tablespoon of dry yeast into a bowl. Mix it well for the yeast to activate. There is no need to rest the mixture. Too much waiting at this stage can make the dough too sour. You can move on to step 2 immediately.

Step 2: Preparing the Dough

Add 3 cups of Wheat flour, 1 teaspoon salt, 1 tablespoons of oil. Add the activated yeast mixture. Additionally you can add 1/4 cup of water to make the dough. Dough making is a very crucial step. Keep kneading the dough for about 15 min, until the dough becomes glutenous. You must be able to stretch the dough into a thin layer. This is important for the dough to have enough room for the gas released while the dough is rising.

Step 3: Resting

Add some oil to the bowl before placing the dough back. Cover the bowl with a cling wrap or a wet towel. Make sure you choose a big enough bowl that can hold double the size of the dough. Set aside for 2 hours after which the dough would have doubled in size.

Step 3: Proofing

After 2 hours, knead the dough again into a cylindrical shape. It should be such that it fits into your baking pan. Grease the pan with some butter or oil. Place the dough now into the baking pan. Cover it with a wet towel or cling wrap and rest for 30 minutes. The dough should rise an inch above the pan.

Step 4: Baking

Brush some milk on the dough. This gives a good brown color. Place the baking pan in a pre-heated oven on a low rack. Bake at 180 degree Celsius for 40 minutes.

Enjoy the aroma of the baking bread spread around your kitchen and home 🙂

Allow the bread to cool and then cut them into slices.

Posted in Cooking is Fun!

Oats Cake

Here’s a healthy version of cake. No maida, no sugar and no guilt!!

Dry ingredients:

  • Quick Oats – 3 cups
  • Cocoa Powder – 1 cup
  • Baking Powder – 1/2 teaspoon
  • Baking soda – 1/4 teaspoon
  • Cinnamon powder – 1/4 teaspoon

I used Quick Oats. Rolled Oats would work as well. The cinnamon powder is optional. Mix the above dry ingredients evenly.

Wet Ingredients:

  • Melted butter (unsalted) – 2 cubes
  • Honey – 1/2 cup or as sweet as you prefer
  • Egg – 1 (whisked)
  • Milk – 1 cup
  • Vanilla extract – 1/4 teaspoon

Mix the above wet ingredients. Add the dry ingredients and combine to form a homogenous mixture.

Smear the cake pan with butter. Dust with Oats powder. Another easier way to avoid preparing oats powder is to just save some dry ingredient that we just prepared and sprinkle the same onto the cake pan. Remove the excess.

Transfer the mixed batter into a cake pan. Tap it a few times. Place it on low rack.

Pre heat microwave at 180 degree Centigrade. Bake for 20-25 minutes until golden brown or until it is completely cooked. Insert a toothpick and it should come out clear without any batter sticking to it.

Make sure to cool it on the rack for about 15 minutes before serving.

Posted in <3 for books

The Lost Symbol – Dan Brown

This one came as a total surprise to my perception of Dan Brown’s Books.
The story revolves around Freemasonry and a hidden secret. Robert Langdon tries to decipher the hidden secret that has been guarded for generations by a family of Masons. The story starts off introducing a fierce villain and how he manipulates Langdon into believing and deciphering the hidden secret. The secret has the potential that can lead to “apocalypse”. Dan Brown creates a plot that sound very real and so very believable. He brings in interesting artifacts like Durer’s painting, Franklin Magic Square that are used to solve puzzles. The novel is thrilling and delivers an edge-of-your-seat suspense until you read two thirds of the book. But as you approach the end, when all the missing parts are explained, it just doesn’t seem to fit together.

The Lost Symbol – Dan Brown

As the story unfolds, the hidden secret which could potentially end the world if revealed, turns out to be something very obvious. The author doesn’t justify the necessity of hiding the secret, which is so easily accessible, behind unbreakable codes and protecting it through ages. However, he takes a lot of time proving that the secret could actually lead to disaster if fallen into the wrong hands. That was quite unconvincing.

The positive aspect of the novel is the plot that sounds so real. The Author uses the thin line between fiction and reality. His efforts in studying the elements of Freemasonry, The Architecture of Monuments, Holy books etc. bringing them into the story are commendable.

I truly enjoyed Dan Brown’s Da Vinci Code. May be it’s hard to write a series in the same genre. It is obviously not so easy to make a novel sound so real and at the same time make it a thriller. May be one’s expectation goes sky high after reading a good book like Da Vinci Code. You expect nothing less. However, I did have a great time reading as much as two thirds of the book and that is all that matters!