How to Build a Message Delivery Status in Django
Tags: python, django, pusher, javascript
Published: 2018-01-08 19:02:32

---
title: How to Build a Message Delivery Status in Django 
published: true
description: How to Build a Message Delivery Status in Django 
tags: Python, Django, Pusher, javascript
---
![](https://blog.pusher.com/wp-content/uploads/2017/05/How-to-build-a-message-delivery-status-in-Django-demo.gif)

Today, we will make a realtime message delivery status framework with Django and Pusher. A basic understanding of Django and Vue is needed in order to follow this tutorial.

## Setting up Django

First, we need to install the Python Django library if we don’t already have it.  
To install Django, we run:

```bash
pip install django
```

After installing Django, it’s time to create our project. Open up a terminal and create a new project using the following command:

```bash
django-admin startproject pusher_message
```

In the above command, we created a new project called `pusher_message`. The next step will be to create an app inside our new project. To do that, let’s run the following commands:

```bash
//change directory into the pusher_message directory 
cd pusher_message 
//create a new app where all our logic would live 
django-admin startapp message
```

Once we are done setting up the new app, we need to tell Django about our new application, so we will go into our `pusher_message\settings.py` and add the message app to our installed apps as seen below:

```python
INSTALLED_APPS = ['django.contrib.admin', 
                  'django.contrib.auth', 
                  'django.contrib.contenttypes', 
                  'django.contrib.sessions', 
                  'django.contrib.messages', 
                  'django.contrib.staticfiles', 
                  'message']
```

After doing the above, it’s time for us to run the application and see if all went well.  

In our terminal shell, we run:

```bash
python manage.py runserver
```

If we navigate our browser to `http://localhost:8000`, we should see the following:

![](https://blog.pusher.com/wp-content/uploads/2017/05/How-to-build-a-message-delivery-status-in-Django.png)

## Set up an App on Pusher

At this point, Django is ready and set up. We now need to set up Pusher, as well as grab our app credentials.  
We need to sign up on [Pusher](https://pusher.com/signup), create a new app, and also copy our secret application key and application id.

![](https://blog.pusher.com/wp-content/uploads/2017/05/How-to-build-a-message-delivery-status-in-Django-2.png)

The next step is to install the required libraries:

```bash
pip install pusher
```

In the above bash command, we installed one package, `pusher`. This is the official Pusher library for Python, which we will be using to trigger and send our messages to Pusher.

## Creating Our Application

First, let us create a model class, which will generate our database structure.  
Let’s open up `message\models.py` and replace the content with the following:

```python
from django.db import models 
from django.contrib.auth.models import User 
# Create your models here. 
class Conversation(models.Model): 
    user = models.ForeignKey(User, on_delete=models.CASCADE) 
    message = models.CharField(blank=True, null=True, max_length=225) 
    status = models.CharField(blank=True, null=True, max_length=225) 
    created_at = models.DateTimeField(auto_now=True)
```

In the above block of code, we defined a model called `Conversation`. The conversation table consists of the following fields:

- A field to link the message to the user who created it
- A field to store the message
- A field to store the status of the message
- A filed to store the date and time the message was created

**Running Migrations**  
We need to make migrations and also run them so our database table can be created. To do that, let us run the following in our terminal:

```python
python manage.py makemigrations python manage.py migrate
```

**Creating Our Views.**  
In Django, the views do not necessarily refer to the HTML structure of our application. In fact, we can see it as our `Controller`, as referred to in some other frameworks.  
Let us open up our `views.py` in our `message` folder and replace the content with the following:

```python
from django.shortcuts import render 
from django.contrib.auth.decorators import login_required 
from django.views.decorators.csrf import csrf_exempt 
from pusher import Pusher 
from .models import * 
from django.http import JsonResponse, HttpResponse 
# instantiate pusher 
pusher = Pusher(app_id=u'XXX_APP_ID', key=u'XXX_APP_KEY', secret=u'XXX_APP_SECRET', cluster=u'XXX_APP_CLUSTER') 
# Create your views here. 
#add the login required decorator, so the method cannot be accessed withour login 
@login_required(login_url='login/') 
def index(request): 
    return render(request,"chat.html"); 
#use the csrf_exempt decorator to exempt this function from csrf checks 
@csrf_exempt 
def broadcast(request): 
    # collect the message from the post parameters, and save to the database 
    message = Conversation(message=request.POST.get('message', ''), status='', user=request.user); 
    message.save(); 
    # create an dictionary from the message instance so we can send only required details to pusher 
    message = {'name': message.user.username, 'status': message.status, 'message': message.message, 'id': message.id} 
    #trigger the message, channel and event to pusher 
    pusher.trigger(u'a_channel', u'an_event', message) 
    # return a json response of the broadcasted message 
    return JsonResponse(message, safe=False) 

#return all conversations in the database 
def conversations(request): 
    data = Conversation.objects.all() 
    # loop through the data and create a new list from them. Alternatively, we can serialize the whole object and send the serialized response 
    data = [{'name': person.user.username, 'status': person.status, 'message': person.message, 'id': person.id} for person in data] 
    # return a json response of the broadcasted messgae 
    return JsonResponse(data, safe=False) 
    
#use the csrf_exempt decorator to exempt this function from csrf checks 
@csrf_exempt 
def delivered(request, id): 
    message = Conversation.objects.get(pk=id); 
    # verify it is not the same user who sent the message that wants to trigger a delivered event 
    if request.user.id != message.user.id: 
        socket_id = request.POST.get('socket_id', '') message.status = 'Delivered'; message.save(); 
        message = {'name': message.user.username, 'status': message.status, 'message': message.message, 'id': message.id} 
        pusher.trigger(u'a_channel', u'delivered_message', message, socket_id) 
        return HttpResponse('ok'); 
    else: 
        return HttpResponse('Awaiting Delivery');
```

In the code above, we have defined four main functions which are:

- `index`
- `broadcast`
- `conversation`
- `delivered`

In the `index` function, we added the login required decorator, and we also passed the login URL argument which does not exist yet, as we will need to create it in the `urls.py` file. Also, we rendered a default template called `chat.html` that we will also create soon.  

In the `broadcast` function, we retrieved the content of the message being sent, saved it into our database, and finally triggered a Pusher request passing in our message dictionary, as well as a channel and event name.  
In the `conversations` function, we simply grab all conversations and return them as a JSON response.

Finally, we have the `delivered` function, which is the function that takes care of our message delivery status.  

In this function, we get the conversation by the ID supplied to us. We then verify that the user who wants to trigger the delivered event isn’t the user who sent the message in the first place. Also, we pass in the `socket_id` so that Pusher does not broadcast the event back to the person who triggered it.

The `socket_id` stands as an identifier for the socket connection that triggered the event.

**Populating The URL’s.py**  
Let us open up our `pusher_message\urls.py` file and replace with the following:

```python
"""pusher_message URL Configuration The `urlpatterns` list routes URLs to views. 
For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ 
Examples: Function views 
1. Add an import: from my_app import views 
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 
Class-based views 
1. Add an import: from other_app.views import Home 
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 
Including another URLconf 
1. Import the include() function: from django.conf.urls import url, include 
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 
""" 
from django.conf.urls import url 
from django.contrib import admin 
from django.contrib.auth import views 
from message.views import * 
urlpatterns = [url(r'^$', index), 
               url(r'^admin/', admin.site.urls), 
               url(r'^login/$', views.login, {'template_name': 'login.html'}), 
               url(r'^logout/$', views.logout, {'next_page': '/login'}), 
               url(r'^conversation$', broadcast), url(r'^conversations/$', conversations), 
               url(r'^conversations/(?P<id>[-\w]+)/delivered$',delivered) 
               ]
```

What has changed in this file? We have added six new routes to the file.  
We have defined the entry point and assigned it to our `index` function. Next, we defined the login URL, which the `login_required` decorator would try to access to authenticate users. 

We have used the default `auth` function to handle it but passed in our own custom template for login, which we will create soon.

Next, we defined the routes for the `conversation` message trigger, all `conversations`, and finally the `delivered` conversation.

**Creating the HTML**  **Files**  
Now we will need to create two HTML pages so our application can run smoothly. We have referenced two HTML pages in the course of building the application.


Let us create a new folder in our `messages` folder called `templates`.  
Next, we create a file called `login.html` in our `templates` folder and replace it with the following:

```html
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
{% if form.errors %} 
<center>
   <p>Your username and password didn't match. Please try again.</p>
</center>
{% endif %} {% if next %} {% if user.is_authenticated %} 
<center>
   <p>Your account doesn't have access to this page. To proceed, please login with an account that has access.</p>
</center>
{% else %} 
<center>
   <p>Please login to see this page.</p>
</center>
{% endif %} {% endif %} 
<div class="container">
   <div class="row">
      <div class="col-md-4 col-md-offset-4">
         <div class="login-panel panel panel-default">
            <div class="panel-heading">
               <h3 class="panel-title">Please Sign In</h3>
            </div>
            <div class="panel-body">
               <form method="post" action="">
                  {% csrf_token %} 
                  <p class="bs-component"> 
                  <table>
                     <tr>
                        <td>{{ form.username.label_tag }}</td>
                        <td>{{ form.username }}</td>
                     </tr>
                     <tr>
                        <td>{{ form.password.label_tag }}</td>
                        <td>{{ form.password }}</td>
                     </tr>
                  </table>
                  </p> 
                  <p class="bs-component"> 
                  <center> <input class="btn btn-success btn-sm" type="submit" value="login" /> </center>
                  </p> <input type="hidden" name="next" value="{{ next }}" /> 
               </form>
            </div>
         </div>
      </div>
   </div>
</div>
Next, let us create the `chat.html` file and replace it with the following: 
<html>
   <head>
      <title> </title>
   </head>
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.2/vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.1/axios.min.js"></script> <script src="//js.pusher.com/4.0/pusher.min.js"></script> 
   <style> .chat { list-style: none; margin: 0; padding: 0; } .chat li { margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px dotted #B3A9A9; } .chat li.left .chat-body { margin-left: 60px; } .chat li.right .chat-body { margin-right: 60px; } .chat li .chat-body p { margin: 0; color: #777777; } .panel .slidedown .glyphicon, .chat .glyphicon { margin-right: 5px; } .panel-body { overflow-y: scroll; height: 250px; } ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); background-color: #F5F5F5; } ::-webkit-scrollbar { width: 12px; background-color: #F5F5F5; } ::-webkit-scrollbar-thumb { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); background-color: #555; } </style>
   <body>
      <div class="container" id="app">
         <div class="row">
            <div class="col-md-12">
               <div class="panel panel-primary">
                  <div class="panel-heading"> <span class="glyphicon glyphicon-comment"></span> Chat </div>
                  <div class="panel-body">
                     <ul class="chat" id="chat" >
                        <li class="left clearfix" v-for="data in conversations">
                           <span class="chat-img pull-left" > <img :src="'http://placehold.it/50/55C1E7/fff&amp;text='+data.name" alt="User Avatar" class="img-circle"/> </span> 
                           <div class="chat-body clearfix">
                              <div class="header"> <strong class="primary-font" v-html="data.name"> </strong> <small class="pull-right text-muted" v-html="data.status"></small> </div>
                              <p v-html="data.message"> </p>
                           </div>
                        </li>
                     </ul>
                  </div>
                  <div class="panel-footer">
                     <div class="input-group"> <input id="btn-input" v-model="message" class="form-control input-sm" placeholder="Type your message here..." type="text"> <span class="input-group-btn"> <button class="btn btn-warning btn-sm" id="btn-chat" @click="sendMessage()"> Send</button> </span> </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </body>
</html>
```

**Vue Component And Pusher Bindings**

That’s it! Now, whenever a new message is delivered, it will be broadcast and we can listen, using our channel, to update the status in realtime. Below is our Example component written using Vue.js.

**Please note: In the Vue component below, a new function called** ` **queryParams** ` **was defined to serialize our POST body so it can be sent as** ` **x-www-form-urlencoded** ` **to the server in place of as a** ` **payload** ` **.**  **We did this because**  **Django cannot handle requests coming in as** ` **payload** `.

```html
<script> 
var pusher = new Pusher('XXX_APP_KEY', { cluster: 'XXX_APP_CLUSTER' });
var socketId = null;
pusher.connection.bind('connected', function () { socketId = pusher.connection.socket_id; });
var my_channel = pusher.subscribe('a_channel');
var config = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } };
new Vue({
    el: "#app", data: { 'message': '', 'conversations': [] }, mounted() {
        this.getConversations();
        this.listen();
    }, methods: {
        sendMessage() { axios.post('/conversation', this.queryParams({ message: this.message }), config).then(response => { this.message = ''; }); }, getConversations() {
            axios.get('/conversations').then((response) => {
                this.conversations = response.data;
                this.readall();
            });
        },
        listen() {
            my_channel.bind("an_event", (data) => {
                this.conversations.push(data);
                axios.post('/conversations/' + data.id + '/delivered', this.queryParams({ socket_id: socketId }));
            }) my_channel.bind("delivered_message", (data) => { for (var i = 0; i < this.conversations.length; i++) { if (this.conversations[i].id == data.id) { this.conversations[i].status = data.status; } } })
        },
        readall() {
            for (var i = 0; i < this.conversations.length; i++) {
                if (this.conversations[i].status == 'Sent') {
                    axios.post('/conversations/' + this.conversations[i].id + '/delivered');
                }
            }
        },
        queryParams(source) {
            var array = [];
            for (var key in source) {
                array.push(encodeURIComponent(key) + "=" + encodeURIComponent(source[key]));
            }
            return array.join("&");
        }
    }
});
</script>
```

Below is the image demonstrating what we have built:

![](https://blog.pusher.com/wp-content/uploads/2017/05/How-to-build-a-message-delivery-status-in-Django-demo.gif)


## Conclusion

In this article, we have covered how to create a realtime message delivery status using Django and Pusher. We have gone through exempting certain functions from CSRF checks, as well as exempting the broadcaster from receiving an event they triggered.  

The code is hosted on a [public GitHub repository](https://github.com/samuelayo/pusher_django_message_delivery). You can download it for educational purposes.  

Have a better way we could have built our application, reservations or comments? Let us know in the comments. Remember, sharing is learning.

---

This post is originally published by the author [here](https://blog.pusher.com/how-to-build-a-message-delivery-status-in-django/).