City Chat with Python, Django and Twilio Programmable Chat

Google Map with current position

Looking for a new apartment in your city? Is it election day and you want to remind people to get out and vote? Want to poll residents for the best restaurant in their neighborhood?

In this blog post, we’ll build a messaging application where messages are broadcast to recipients based on the city reported by their browser location. Having everyone in your city join together in a chat application may seem crazy, but buckle up, because we’re going to give it a try. By combining Python, Django, Twilio Programmable Chat and the Google Maps API we’ll take our best shot at making it easier to find an apartment, remind your neighbors to vote or poll your fellow city residents.

Tools We’ll Need

Before we dive into building our neighborhood-based chat application, let’s take a look at the tools we’ll be using throughout this blog post.

  1. The 1.9 version of the Django web framework
  2. Twilio Python helper library >= version 5.0.0
  3. A free Twilio account and the Twilio Programmable Chat public beta API
  4. Google’s Maps API for determining what city a user is located in

Don’t worry about installing these tools just yet – we’ll handle that in the sections below as we go through this tutorial. If you want to see the completed code from this blog post at any time check out the GitHub repository with the finished project. Before we start writing our code though let’s walk through preparing our Python environment for development.

Python Environment Setup

Our prep work starts with getting our Python development environment ready to build our neighborhood-based chat application. In this section we’ll handle the following steps to make sure you’re ready to run the code:

  1. Check that Python is installed correctly
  2. Set up and activate a new virtualenv
  3. Install required Python dependencies into our virtualenv using pip

Setup Step 1: Check the Python installation

If you’re on Mac OS X or Linux, you’ve already got Python installed on your system. Check the version by going into the terminal and typing python —version. You’ll see the precise version number, something like Python 2.7.6. If you’re on Python 2.6 or greater, you’re good to go.

For Windows, make sure you download and install the Python .exe installation package. Either Python 2 or 3 is fine. If you want further information about the 2 versus 3 version debate, there’s plenty more information written by experienced Python developers on the topic. My recommendation is to use Python 3.5 unless you have a pressing reason to use an earlier version, because the Python community is now migrating to Python 3.
Once Python is installed on your system and you can run it with the python command, we’re ready to set up a virtualenv.

Setup Step 2: Set up a new virtualenv and activate it

Virtualenv is a dependency isolation library that makes it much easier to switch between Python versions and various code packages you’re using on different projects. Create a virtualenv in a location you’ll remember using the following command. In my case, I have a folder called Envs under my home directory that keeps my virtualenvs organized.

We have our virtualenv activated and we should see our command prompt show the name of our virtualenv, such as (citychat)$. With the virtualenv in place and activated, we can use pip to install our dependencies.

Setup Step 3: Install dependencies with pip

pip handles Python library installations. With your virtualenv still activated, run the following command to install our Django and Twilio library dependencies.

You may be able to install the very latest versions of the Django and Twilio packages but it’s preferable to peg dependencies to specific version numbers just in case there are future backwards-incompatible modifications. With those libraries installed, we can start building our Django project that will run the chat application.

Starting Our Django Project

We now have Django installed along with the django-admin command. django-admin helps get the boilerplate directories and code created for our project. Run the following two commands to start the project and change into the newly created directory.

We also need to create an app within the project with the following command.

Django’s just created a whole bunch of files for us. To make sure we’ve got the right set up, ensure the created folders look like the directory and subdirectories in the following image.

citychat.png

We’ll also have __init__.py, settings.py, urls.py and wsgi.py files created for us in the citychat/citychat subdirectory. Our tutorial will modify most of these files along the way. If you’re unfamiliar with why Django created these folders and files be sure to go through the Django quickstart for more context.

We need to modify a few lines in settings.py and set up our environment variables to connect our Django project to our Twilio settings.

Add the following line to the INSTALLED_APPS list in settings.py to add the chat app to our Django project.

Before you close settings.py, append the following lines to the file so we can establish the necessary Twilio Programmable Chat settings we’ll use a bit later in the post.

Next we need to set up our environment variables so the os module can get them when our Django project starts up. How to set environment variables depends on your operating system. Here are some handy guides if you’re on Windows, Mac OS X or Ubuntu Linux that’ll help with the specific steps.

Five environment variables will need to be set:

  • TWILIO_ACCOUNT_SID – found on your Twilio account dashboard
  • TWILIO_AUTH_TOKEN – also found on your Twilio account dashboard; not required by our Django app but will be used to create the Programmable Chat Service SID later
  • TWILIO_API_KEY – an API key created specifically for this application
  • TWILIO_API_SECRET – a secret string just for this API key
  • TWILIO_IPM_SERVICE_SID – a unique identifier registered with Twilio for our Programmable Chat application

On Mac OS X and Linux, you can set environment variables with the export shell command. Typically, I store these environment variables in a .env file at the root directory of the project during development. We haven’t yet created the TWILIO_API_KEY, TWILIO_API_SECRET or TWILIO_IPM_SERVICE_SID credentials yet though, so let’s walk through how to create those now.
In order to get chat working we have to handle our remaining environment variables.

Let’s create and then set the TWILIO_API_KEY and TWILIO_API_SECRET now. Head into the Twilio portal and click on Dev Tools in the navigation bar.
click-dev-tools.jpg
Click the Dev Tools link in the top navigation bar. We’re taken to the API Explorer but we want to go to the API Keys page. Click the API Keys link in the secondary nav bar.

api-key-nav.jpg

Click the “Create an API Key” button. We’re taken to the following page where we can given our new API key a friendly name. Call it “City Chat” and click the “Create API Key” button.

new-api-key.jpg

After pressing the “Create API Key” button you’ll be taken to a page with the new Sid and Secret tokens are presented. Make sure to copy the Secret token as it will not be displayed again after you exit from this page.
save-api-key.png

There’s one more environment variable called TWILIO_IPM_SERVICE_SID that we’ll need to create via the command line before we have everything ready to modify our .env file.

There are two ways to create this IPM Service and grab the SID. One way is to go to the Programmable Chat Services page, click the “Create an Programmable Chat Service” button, enter a friendly name and let Twilio produce the new service. The second way is to programmatically generate the service via an API call. Let’s use the second method and create the IPM Service from the command line using the following cURL command. The IPM Service we create will include a SID value we need to specify in our TWILIO_IPM_SERVICE_SID environment variable.

Make sure your $TWILIO_ACCOUNT_SID and $TWILIO_AUTH_TOKEN environment variables are still set if you receive an error. If not, your response should look something like the following JSON string.

We need the string that is the value to the “sid” key for our TWILIO_IPM_SERVICE_SID environment variable. With the final value created, we can set all of the environment variables for our application.

It’s time to test out our application to make sure our environment settings are in place and that we’re on a solid foundation to keep coding. From the root directory of our project where manage.py is located, run the Django development server.

With your favorite web browser go to http://127.0.0.1:8000 and you should see the following default success page.
django-it-worked.jpg
Our basic application files and environment variables are set up. Now let’s build a web page with a map that’ll serve our chat application.

Mapping Our Location

Now we can flesh out the code for our chat app within the citychat Django project. Start by updating the chat/views.py file with the following two highlighted lines. This function will look for the chat.html template and render the template after we create the file.

Create a new file named chat/urls.py with the following code to route our application’s default endpoint to the chat view function we just wrote.

The urls.py file maps routes for our application to views in the views.py file. In our case, there is a single url for the chat function that matches the root url for the server.

In order for the chat/urls.py to be found, we need to update the citychat/citychat/urls.py file, which handles url mappings for every app in a Django project. Update the following two lines so this urls.py file can match with the chat/urls.py routes.

One more file needs to be created then we can give our application a quick test. Create a directory under citychat/chat named templates. Under templates create a file that matches up with the name of the Django template used in the chat function, which in our case is chat.html. Write or copy the following markup into the new chat.html file.

In the above template, we’re setting the stage to display a map showing our current location with a chat box below the map.

There is a referenced JavaScript file, chat.js, that is not hosted externally so it doesn’t exist yet in our Django project. Since that file won’t load our page would look like the screenshot below if we accessed it now. If you pull up http://localhost:8000 in Chrome you can see in the Chrome DevTools that a couple of files are not loading.
city-chat-first-loaded.png

We need to fix that missing file to get our map displayed and determine the city in which we should be chatting.

Create a directory under citychat/chat named static that will hold our static files. Under citychat/chat/static create one more subdirectory named js. Create the file named chat.js under citychat/chat/static/js with the following code.

Head back to your web browser and refresh http://localhost:8000/. Make sure to click “Allow” or “Accept” when asked by the browser whether or not you want to share your current location. For example, in Chrome you’d click accept to the little dropdown from the URL bar.

click-allow.jpg

Okay, now we’ve got the user’s latitude and longitude displayed on the page! However, there’s still that ugly gray box taking up space where our map should be.
lat-long-browser.png
Let’s initialize a map using the user’s location and translate the latitude and longitude into a marker on our page. Modify the citychat/chat/static/js/chat.js JavaScript file we were just working on that obtained the user’s browser location.

city-chat-second-loaded.jpg

Hey now, that looks a little bit better! Unfortunately, chat is still not yet working.

Adding Chat via Programmable Chat

Our embedded map may look slick but chat has yet to land in our app. Earlier in this post we set all the environment variables we need to hook Twilio IP Messaging into our application. With those credentials in place we can now create JSON Web Tokens (JWT) on the server that will be requested by the browser and sent as soon as they are generated. Our application will need a new endpoint though for the browser to request the token by so open up citychat/chat/urls.py and add the single line highlighted below.

The new line added to urls.py creates a /token endpoint for our application and references a views.token function. We need to write that token function in the citychat/chat/views.py file as highlighted below.

In the above code, we include a few new imports for our project’s settings and the JsonResponse from the Django library. The AccessToken and IpMesssagingGrant are specific to Twilio Programmable Chat. They allow us to create the appropriate JWT token to return to the requesting client based on our Twilio account credentials stored in our environment variables.

What about the front end browser code that calls for the access token? We need to take care of that now by modifying citychat/chat/static/js/chat.js with the following highlighted lines. These two functions, print and printMessage are just to help us attach the information messages and chat messages that need to be displayed on the page.

‘); if (asHtml) { $msg.html(infoMessage); } else { $msg.text(infoMessage); } $chatWindow.append($msg); } // Helper function to print chat message to the chat window function printMessage(fromUser, message) { var $user = $(‘‘).text(fromUser + ‘: ‘); if (fromUser === username) { $user.addClass(‘me’); } var $message = $(‘‘).text(message); var $container = $(‘

‘); $container.append($user).append($message); $chatWindow.append($container); } function positionFound(position) { document.getElementById(‘lat’).value = position.coords.latitude; document.getElementById(‘long’).value = position.coords.longitude; mapAndChat(); } // creates the map based on user’s browser location function drawMap() { var mapCanvas = document.getElementById(‘map’); var latLng = new google.maps.LatLng(document.getElementById(‘lat’).value, document.getElementById(‘long’).value); var mapOptions = { center: latLng, zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP } var map = new google.maps.Map(mapCanvas, mapOptions); var marker = new google.maps.Marker({ position: latLng, map: map, title: ‘Your location’ }); } function mapAndChat() { drawMap(); // chat initialization will go here } if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(positionFound); } else { alert(‘It appears that required browser geolocation is not enabled.’); }

With our helper printing functions in place, let’s begin adding the JavaScript chat code that connects our application with the Programmable Chat service. Begin by adding several variables to store a reference to the chat window, the Twilio Programmable Chat service, the messaging channel for our city, the user’s location and the name of the city based on the latitude and longitude from the user’s browser.

Make these changes by continuing to add the following highlighted lines of JavaScript to citychat/chat/static/js/chat.js.

‘); if (asHtml) { $msg.html(infoMessage); } else { $msg.text(infoMessage); } $chatWindow.append($msg); } // Helper function to print chat message to the chat window function printMessage(fromUser, message) { var $user = $(‘‘).text(fromUser + ‘: ‘); if (fromUser === username) { $user.addClass(‘me’); } var $message = $(‘‘).text(message); var $container = $(‘

‘); $container.append($user).append($message); $chatWindow.append($container); } function positionFound(position) { document.getElementById(‘lat’).value = position.coords.latitude; document.getElementById(‘long’).value = position.coords.longitude; mapAndChat(); } // creates the map based on user’s browser location function drawMap() { var mapCanvas = document.getElementById(‘map’); var latLng = new google.maps.LatLng(document.getElementById(‘lat’).value, document.getElementById(‘long’).value); var mapOptions = { center: latLng, zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP } var map = new google.maps.Map(mapCanvas, mapOptions); var marker = new google.maps.Marker({ position: latLng, map: map, title: ‘Your location’ }); } function mapAndChat() { drawMap(); chatBasedOnCity(); } function chatBasedOnCity() { var latitude = $(‘#lat’).val(); var longitude = $(‘#long’).val(); $.getJSON(‘http://maps.googleapis.com/maps/api/geocode/json?latlng=’ + latitude + ‘,’ + longitude + ‘&sensor=true’, {}, function(locationData) { userLocation = locationData.results[0][“formatted_address”]; username = userLocation.replace(/\s/g, ‘_’); city = locationData.results[0].address_components[3].long_name; createChat(); }); } function createChat() { $.getJSON(‘/token’, {identity: username, device: ‘browser’}, function(data) { print(‘It looks like you are near: ‘ + ‘‘ + userLocation + ‘‘, true); accessManager = new Twilio.AccessManager(data.token); messagingClient = new Twilio.IPMessaging.Client(accessManager); var promise = messagingClient.getChannelByUniqueName(city); promise.then(function(channel) { cityChannel = channel; if (!cityChannel) { // If channel does not exist then create it messagingClient.createChannel({ uniqueName: city, friendlyName: city }).then(function(channel) { console.log(‘Created channel:’); console.log(channel); cityChannel = channel; }); } else { console.log(‘Found channel:’); console.log(cityChannel); } }); }); } if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(positionFound); } else { alert(‘It appears that required browser geolocation is not enabled.’); }

The new function chatBasedOnCity takes the latitude and longitude of the user’s browser and looks up the location with the Google Maps API. Google Maps returns JSON with an approximate address for the latitude and longitude along with the city name. We use the city name and the address in the createChat function. createChat obtains the a JWT access token created by our /token endpoint then uses that to authenticate with Twilio Programmable Chat. We then try to create create a channel for our city, but if one already exists we don’t need to create a new channel, we simply log that it exists.

We can test out the above code, but it won’t yet join us to the city messaging channel we need to chat with other users in the same city. Time to finish our JavaScript by joining the new or existent chat channel. Add the highlighted lines below that join our city chat channel and listen for new messages that are sent on it.

‘); if (asHtml) { $msg.html(infoMessage); } else { $msg.text(infoMessage); } $chatWindow.append($msg); } // Helper function to print chat message to the chat window function printMessage(fromUser, message) { var $user = $(‘‘).text(fromUser + ‘: ‘); if (fromUser === username) { $user.addClass(‘me’); } var $message = $(‘‘).text(message); var $container = $(‘

‘); $container.append($user).append($message); $chatWindow.append($container); } // creates the map based on user’s browser location function drawMap() { var mapCanvas = document.getElementById(‘map’); var latLng = new google.maps.LatLng(document.getElementById(‘lat’).value, document.getElementById(‘long’).value); var mapOptions = { center: latLng, zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP } var map = new google.maps.Map(mapCanvas, mapOptions); var marker = new google.maps.Marker({ position: latLng, map: map, title: ‘Your location’ }); } function positionFound(position) { document.getElementById(‘lat’).value = position.coords.latitude; document.getElementById(‘long’).value = position.coords.longitude; mapAndChat(); } function mapAndChat() { drawMap(); chatBasedOnCity(); } function chatBasedOnCity() { var latitude = $(‘#lat’).val(); var longitude = $(‘#long’).val(); $.getJSON(‘http://maps.googleapis.com/maps/api/geocode/json?latlng=’ + latitude + ‘,’ + longitude + ‘&sensor=true’, {}, function(locationData) { userLocation = locationData.results[0][“formatted_address”]; username = userLocation.replace(/\s/g, ‘_’); city = locationData.results[0].address_components[3].long_name; createChat(); }); } function createChat() { $.getJSON(‘/token’, {identity: username, device: ‘browser’}, function(data) { print(‘It looks like you are near: ‘ + ‘‘ + userLocation + ‘‘, true); accessManager = new Twilio.AccessManager(data.token); messagingClient = new Twilio.IPMessaging.Client(accessManager); var promise = messagingClient.getChannelByUniqueName(city); promise.then(function(channel) { cityChannel = channel; if (!cityChannel) { // If channel does not exist then create it messagingClient.createChannel({ uniqueName: city, friendlyName: city }).then(function(channel) { console.log(‘Created channel:’); console.log(channel); cityChannel = channel; setupChannel(); }); } else { console.log(‘Found channel:’); console.log(cityChannel); setupChannel(); } }); }); function setupChannel() { // Join the general channel cityChannel.join().then(function(channel) { print(‘Joined channel “‘ + channel.uniqueName + ‘” as ‘ + ‘‘ + username + ‘.’, true); }); // Listen for new messages sent to the channel cityChannel.on(‘messageAdded’, function(message) { printMessage(message.author, message.body); }); } // Send a new message to the general channel var $input = $(‘#chat-input’); $input.on(‘keydown’, function(e) { if (e.keyCode == 13) { cityChannel.sendMessage($input.val()) $input.val(”); } }); } if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(positionFound); } else { alert(‘It appears that required browser geolocation is not enabled.’); }

The above setupChannel function joins the channel based on our current city’s name and listens for new messages sent on the channel. We also enable chat input to send messages on our channel so other clients listening for messages on the channel will see and display them on the screen.

Go ahead and refresh the localhost:8000 page in your browser to test out the results.

City Chatting Away

Congratulations!  Our application is now running on our localhost machine on port 8000. When you want to open up the app to other folks without access to your system, you can use a localhost tunneling tool like Ngrok or deploy it to a server with a fixed IP address.

If you’re looking for more Python code to implement Programmable Chat into your application, check out the Programmable Chat JavaScript Quickstart that includes a Python with Flask web application tutorial.

We can now use our application to chat with other residents that join in the same Programmable Chat channel based on the city name gleaned from our browser location. Have a few folks from your city join the app and try it out to find an apartment, get out the vote or poll fellow city residents. What other situations do you think Programmable Chat could be applied to? I’d like to hear from you if you run into any issues throughout the post so feel free to drop a comment below or contact me via:

  • Hi Matt, it was an interesting session last night (GMT night). It is really interesting to be able to create such an app without having to worry about redis, websockets, etc.
    I hope I will have some time this weekend to give it a try.
    Thank you for putting this together!

    PS I also need to wrap my head around the pricing scheme for the Twilio IP so that I do not end up over budget :-)

    • Thanks Răzvan! Be sure to check out the other Developer Evangelists’ blog posts as well coming out this week. Pricing will depend on your application’s scale, but give it a try with your trial account and you’ll be able to use IP Messaging for free while you’re developing.

  • Vandna

    Python is simple, easy to learn syntax emphasizes readability and therefore reduces the cost of program maintenance. So is is good to make city chat with it.

    http://www.warofthedestiny.com/sidh-indrajaal-mantra-for-money/