[Part-1] Creating a Simple Chat App with DjangoRestFramework

in utopian-io •  7 years ago 


Source
In this tutorial, I would like to explain how to create a very simple Live Chat application with the help of Django Rest Framework & AJAX.

Django Rest Framework is a very popular framework in django. It's a good idea to learn DRF if you are onto backend development with Django. DRF makes creating RESTful APIs easy as pie!

Note: The reader is expected to have some idea on basic concepts of django like Models, Views etc. I am using the new Django 2.0 in this tutorial.

In addition to DRF I will use the following Libraries for the specified purposes:

  • MaterializeCSS - For designing the web UI
  • Jquery - For AJAX
  • Django Widget Tweaks - A useful module for handling forms easier in Django

Lets Begin...

1. Directory and environment setup

It is a good practice to use a virtual environemnt while you are creating a new project. Helps you track the requirements for the project and also the global version of packages are kept unaffected.

To create a virtual environemnt we use virtualenv. If it is not already installed in your PC then just installl itusing pip.

    pip install virtualenv

Before creating the virtualenv lets create a directory for our project. For that first of all, lets put a name for our Application. Let it be ChatApp (I know it could have been better. But nothing creative comes into my mind now! Lol)

Create a directory and cd into it.

    mkdir ChatApp
    cd ChatApp

To create a virtualenv, just specify the name of the environement after the virtualenv command in terminal like this:

virtualenv chat-env

Activate the environment and install required python packages

source chat-env/bin/activate
pip intsall django djangorestframework django-widget-tweaks

After everything is successfully installed, to create a project just open a terminal in a directory and type the following command:

django-admin startproject ChatApp .

Mind the '.' after ..ChatApp, it is included because we dont want it to create a new directory. So the above command creates a directory ChatApp with some files and a file manage.py, required for a basic django application. If everything went well, your directory structure will look like this:

ChatApp
├── ChatApp
├── chat-env
├── manage.py

To check everything is working, just type to start the development server:

    ./manage.py runserver

...And check the address 127.0.0.1:8000 in a browser. It will show something like this:
Django.png

2. App Setup

You are expected to know that in every independent module in django is considered as an app. So we need to create an app for Chat.

To create 'chat' app

./manage.py startapp chat

Now lets create a model for our messages:

ChatApp/chat/models.py

from django.contrib.auth.models import User
from django.db import models
class Message(models.Model):
     sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sender')        
     receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='receiver')        
     message = models.CharField(max_length=1200)
     timestamp = models.DateTimeField(auto_now_add=True)
     is_read = models.BooleanFeild(default=False)
     def __str__(self):
           return self.message
     class Meta:
           ordering = ('timestamp',)
  • sender is used to identify the sender of the message
  • receiver is used to identify the receiver of the message
  • message is a CharField to store the text.
  • timestamp stores the date and time of creation of message
  • is_read keeps track if the message is read or not.

Ordering is set such that it follows the timestamp. ie. The latest message will be the last one on the list.

We make use of the User model which is builtin model in Django, for managing users. We need just the username field and password from it, for our basic application.

So that's all the tables we need. Now lets migrate our changes to the database. But before that we need to add the apps to INSTALLED_APPS list in ChatApp/settings.py

INSTALLED_APPS = [
    .
    .
    'widget_tweaks',
    'rest_framework',
    'chat'
]

Apply migrations:

./manage.py makemigrations
./manage.py migrate

Now lets make the serializers for the models.

Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

ChatApp/chat/serializers.py

from django.contrib.auth.models import User
from rest_framework import serializers
from chat.models import Message
# 
# User Serializer
class UserSerializer(serializers.ModelSerializer):
    """For Serializing User"""
    password = serializers.CharField(write_only=True)
    class Meta:
        model = User
        fields = ['username', 'password']
# 
# Message Serializer
class MessageSerializer(serializers.ModelSerializer):
    """For Serializing Message"""
    sender = serializers.SlugRelatedField(many=False, slug_field='username', queryset=User.objects.all())
    receiver = serializers.SlugRelatedField(many=False, slug_field='username', queryset=User.objects.all())
    class Meta:
        model = Message
        fields = ['sender', 'receiver', 'message', 'timestamp']

Let's see one by one....

We import the serializers from rest_framework to make use of the serializer classes.

The ModelSerializer class provides a shortcut that lets you automatically create a Serializer class with fields that correspond to the Model fields.

In UserSerializer, password is specified write_only to avoid gettting the password displayed on GET request. We only need it on POST request in case of creation of a new user.

The sender and receiver of Message is serialized as SlugRelatedField to represent the target of the relationship using a field on the target. The field is specified as slug_field. It also takes a 'many' argument which identifies the relation is many-to-many or not. We gave it false, since a message can only contain a single sender and receiver.
The 'queryset' argument takes the list of objects from which the related object is to be chosen.

Associating Views to serializers

Now lets create some views to return our serialized data.

User View
ChatApp/chat/views.py

from django.contrib.auth.models import User                                # Django Build in User Model
from django.http.response import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from chat.models import Message                                                   # Our Message model
from chat.serializers import MessageSerializer, UserSerializer # Our Serializer Classes
# Users View
@csrf_exempt                                                              # Decorator to make the view csrf excempt.
def user_list(request, pk=None):
    """
    List all required messages, or create a new message.
    """
    if request.method == 'GET':
        if pk:                                                                      # If PrimaryKey (id) of the user is specified in the url
            users = User.objects.filter(id=pk)              # Select only that particular user
        else:
            users = User.objects.all()                             # Else get all user list
        serializer = UserSerializer(users, many=True, context={'request': request}) 
        return JsonResponse(serializer.data, safe=False)               # Return serialized data
    elif request.method == 'POST':
        data = JSONParser().parse(request)           # On POST, parse the request object to obtain the data in json
        serializer = UserSerializer(data=data)        # Seraialize the data
        if serializer.is_valid():
            serializer.save()                                            # Save it if valid
            return JsonResponse(serializer.data, status=201)     # Return back the data on success
        return JsonResponse(serializer.errors, status=400)     # Return back the errors  if not valid

Message View
ChatApp/chat/views.py

@csrf_exempt
def message_list(request, sender=None, receiver=None):
    """
    List all required messages, or create a new message.
    """
    if request.method == 'GET':
        messages = Message.objects.filter(sender_id=sender, receiver_id=receiver)
        serializer = MessageSerializer(messages, many=True, context={'request': request})
        return JsonResponse(serializer.data, safe=False)
    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = MessageSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

The above view is pretty same as the User view, except it requires sender and receiver as URL parameters. The sender and receiver expects the id of the users, associated with the message.

We can now, associate urls with these views. So lets create urls.py file inside our chat directory and include it in the main urls.py in the ChatApp/ChatApp directory. To incude the new chat/urls.py just add a new line to the urlpatterns list in ChatApp/urls.py

urlpatterns = [
    ...
    path('', include('chat.urls')),
]

One of the new features in Django 2.0 is the introduction of path() function, which is a substitute for the old url() function. The main advantage is that we can specify URL arguments without any complex regular expressions. You can read more about it here : Django 2.0 Release Notes

Edit the chat/urls.py to point our views.

from django.urls import path
from . import views
urlpatterns = [
    # URL form : "/api/messages/1/2"
    path('api/messages/<int:sender>/<int:receiver>', views.message_list, name='message-detail'),  # For GET request.
    # URL form : "/api/messages/"
    path('api/messages/', views.message_list, name='message-list'),   # For POST
    # URL form "/api/users/1"
    path('api/users/<int:pk>', views.user_list, name='user-detail'),      # GET request for user with id
    path('api/users/', views.user_list, name='user-list'),    # POST for new user and GET for all users list
]

Now create a super user for our site.

./manage.py createsuperuser

Give required details and create the superuser. Now you can login to admin panel through : http://127.0.0.1:8000/admin after running the development server.

To make the Messages to show up in the admin panel. Add register the model in

chat/admin.py

from django.contrib import admin
from chat.models import Message
admin.site.register(Message)

Now the Messages will show up in the admin panel. Try creating some message with the same user as sender and receiver.

You can check the working of API using curl after creating some messages, like this.

curl http://127.0.0.1:8000/api/messages/1/1

Will return something like this (Here I have created 3 messages with same sender and receiver):

[{"sender": "ajmal", "receiver": "ajmal", "message": "Hai", "timestamp": "2017-12-16T14:38:03.096207Z"}, {"sender": "ajmal", "receiver": "ajmal", "message": "Hai", "timestamp": "2017-12-16T16:01:46.433918Z"}, {"sender": "ajmal", "receiver": "ajmal", "message": "New message", "timestamp": "2017-12-18T15:49:30.333146Z"}]

To get the list of users

curl http:127.0.0.1:8000/users/

Will return the list of users like this :

[{"username": "ajmal"}]

So that's all for now. We have setup already the 90% of the backend required for our ChatApp. We shall learn to build a front end structure for our app in the next part. I have added the above code in Github. You can check it out here : Github DRF-Chat

Thank you.



Posted on Utopian.io - Rewarding Open Source Contributors

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Very informative. Your explanation is clear and concise. What any programmer looking for a solution wants.

Thank you :-)

Hey @ajmaln I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • You are generating more rewards than average for this category. Super!;)
  • Seems like you contribute quite often. AMAZING!

Suggestions

  • Contribute more often to get higher and higher rewards. I wish to see you often!
  • Work on your followers to increase the votes/rewards. I follow what humans do and my vote is mainly based on that. Good luck!

Get Noticed!

  • Did you know project owners can manually vote with their own voting power or by voting power delegated to their projects? Ask the project owner to review your contributions!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Nice, in-depth tutorial! More web development tutorials!
I just wish Steemit had a nicer way to format code in blog posts.
Any chance of such feature being added?

Exactly! I have requested the devs to add it soon. It is a very necessary feature.

Oh, that's terrific news! Thanks for making that request. ;)
Perhaps later we'd also have syntax highlighting for different languages which would be amazing and would make tutorials like these stand out and be more easier to navigate/follow.

This post has received gratitude of 1.00 % from @jout

Thank You!! This is a perfect Django challenge for me(beginner ).

Qurator
Your Quality Content Curator
This post has been upvoted and given the stamp of authenticity by @qurator. To join the quality content creators and receive daily upvotes click here for more info.

Qurator's exclusive support bot is now live. For more info click HERE or send some SBD and your link to @qustodian to get even more support.

Whoa! Great job bro.

The online status doesn't work, I tried to fix it but failed. You should perhaps review it.