In this guest post, Vuyisile Ndlovu explores building a Divio web application that uses Geolocation to determine where a user is located. Read up on his experience here.
Michael Nicholson
Cloud Solution Engineer
In this article, our contributor Vuyisile Ndlovu shows how to build and deploy a Divio application that makes use of geolocation, functionality that allows a website to determine where in the world a user is. This example uses Django, but the principles will apply to any stack.
This article shows how to:
handle HTTP headers in a cloud environment
use a REST API with authentication
integrate the free ip-api geolocation API
integrate the TomTom Maps API
Along the way we'll point out some good-practice ways of working with projects of this kind.
Create a new Django project in the Control Panel, and set it up it locally (divio app setup
...).
To make API calls and get responses, we’ll use the well-known Python Requests library.
Add requests
to the project's requirements.in
file. It is always recommended to pin dependencies; at the time of writing, the latest version of Requests is 2.23.0:
# <INSTALLED_ADDONS> # Warning: text inside the INSTALLED_ADDONS tags is auto-generated. Manual changes will be overwritten.
[...]
# </INSTALLED_ADDONS>
requests==2.23.0
The next time this project is built, requests will be collected and installed. Make sure to list packages outside the automatically generated # <INSTALLED_ADDONS>
section.
Create a new Django application by running the following in your terminal:
docker-compose run --rm web python manage.py startapp iplocation
This starts the web container and runs python manage.py startapp
inside it to create a new app called iplocation.
> (The --rm
flag means: remove the container when exiting.)
Add the newly created app to settings.py
:
INSTALLED_APPS.extend([
'iplocation',
])
The new applications needs a view function to process the geolocation data. Edit the views.py
file in the iplocation
app and create a new view:
from django.shortcuts import render
import requests
def home(request):
response = requests.get('http://ip-api.com/json/')
geodata = response.json()
return render(request, 'home.html', {
'ip': geodata['query'],
'country': geodata['country']
})
This view uses requests
to perform a GET
request to http://ip-api.com/json/. This website returns IP location data in JSON format. The response data is read into the geodata
variable and looks something like this:
The view returns the IP address and country data from geodata
to the context and then renders the home.html
template.
Create two HTML templates, base.html
and home.html
in your project's templates directory and add the following to them:
First base.html
:
<html>
<head>
<title>Geolocation Service</title>
<style>
body{
max-width: 600px;
margin: auto;
font-family: "Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color:#212529;
text-align: left;
background-color:#fff;
}
h1 {
text-align: center;
}
</style>
</head>
<body>
<h1>Geolocation Service</h1>
<hr/>
{% block content %}
{% endblock %}
</body>
</html>
And then home.html
:
{% extends 'base.html' %}
{% block content %}
<p>Your IP address is <strong>{{ ip }}</strong> and you are probably in <strong>{{ country }}</strong>. </p>
{% endblock %}
Edit urls.py
to add URL mappings for the views in the iplocation application:
from django.conf.urls import url, include
from django.urls import path
from iplocation import views
from aldryn_django.utils import i18n_patterns
import aldryn_addons.urls
urlpatterns = [
path('', views.home, name='home'),
] + aldryn_addons.urls.patterns() + i18n_patterns(
*aldryn_addons.urls.i18n_patterns()
)
This will display the home view you created above in views.py
.
Run:
docker-compose up web
in the terminal. If everything has gone to plan, you should see something similar to this in your browser at http://localhost:8000 (or http://127.0.0.1:8000):
Geolocation appThe location that is displayed will depend on your IP address and approximate location. To confirm that this displays a different result when a user’s IP changes, here’s the result after I connect to a VPN:
Display a map of the user’s location
To develop the geolocation project further, let's display a map showing the user’s location. For this we’ll use a mapping API. In this example, we'll use the TomTom Maps API.
Like many APIs (and unlike the one we used to obtain the geolocation data) this API requires an API key to authenticate the requests we make to it. Register on the TomTom website and obtain an API key before proceeding further.
Next, add the API key to your local environment as an environment variable. Environment variables are dynamic values that can be used by the processes or applications running on the server. One of the advantages in using them is that you can isolate specific values from your codebase. Environment variables are a good place for storing instance-specific configuration, such as Django settings and API keys that you don’t wish to hard-code into your project.
For local development, use the .env-local
file to store environment variables. Edit it and add your TomTom API key:
API_KEY=Sx7F4LkTXMxb5MdVXC8fcDrPsilYrGffp
Then add an <iframe>
to display a map in the home.html
template:
<iframe width="512"
height="512"
frameborder="0"
style="border:0"
src="https://api.tomtom.com/map/1/staticimage?key={{ api_key }}&zoom=4¢er={{ longitude }}, {{ latitude }}&format=png&layer=basic&style=main&view=IN"
allowfullscreen>
</iframe>
An iframe is an inline frame that is commonly used to include content from another source in an HTML document. The iframe in this template has a width and height of 512px and expects three values to be passed to it from the context; an api_key
, a longitude
, and a latitude
.
The longitude
and latitude
can be extracted from geodata in the view. The API Key will be read from the environment variables. Modify the views.py
file to create a new context dictionary that contains the api_key
, longitude
and latitude
key value pairs:
from django.shortcuts import render
import requests
import os
def home(request):
response = requests.get('http://ip-api.com/json/')
geodata = response.json()
context = {
'ip': geodata['query'],
'country': geodata['country'],
'latitude': geodata['lat'],
'longitude': geodata['lon'],
'api_key': os.environ['API_KEY'],
}
return render(request, 'home.html', context)
The new view and template will add a map to our application:
It's time to push the code up to the Divio cloud and deploy our geolocation application. There are a few steps to take before our project is ready for production.
The code to determine IP location works locally because the application has direct access to the HTTP headers from the client. On the cloud however, the application is not directly exposed to clients: it's always behind Divio's load-balancers. This means that the IP addresses it sees will be those of the load-balancers - not the actual visitors - and it will report the wrong locations.
To address this, the application needs to look at the request headers and extract the original client IP address from there. The X_FORWARDED_FOR
(link) header is a common way of identifying the originating IP address of a client. Some Divio projects may also be protected by Cloudflare; in those case, the header will be CF_CONNECTING_IP
.
So, the view needs an amendment to get hold of the IP address reliably:
def home(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
cloud_flare_ip = request.META.get('HTTP_CF_CONNECTING_IP')
if cloud_flare_ip:
user_ip = cloud_flare_ip
elif x_forwarded_for:
user_ip = x_forwarded_for.split(',')[0]
else:
user_ip = request.META.get('REMOTE_ADDR')
response = requests.get(f'http://ip-api.com/json/{user_ip}')
request.session['geodata'] = response.json()
geodata = request.session['geodata']
context = {
'ip': geodata['query'],
'country': geodata['country'],
'latitude': geodata['lat'],
'longitude': geodata['lon'],
'api_key': os.environ['API_KEY'],
}
return render(request, 'home.html', context)
In order to use the TomTom Maps API in the cloud, add the same API key you added to the .env-local
file to the control panel. In the project, select Environment Variables. Enter the keys and values, and Save.
Commit and push changes:
git add templates iplocation settings.py urls.py
git commit -m “Add iplocation application”
git push origin master
Deploy the Test Server:
divio app deploy test
and to open the Test site:
divio app test
Geolocation is very useful web functionality, that finds many different applications. This project puts a simple example into practice.
And as well as the key work required to do that in a cloud environment - handling HTTP headers, authenticating to a REST API, integrating IP geolocation and a mapping service - we've covered several other important points too:
installing Python packages on Divio
local development and cloud deployment
managing and using environment variables locally and on the cloud