Building interactive maps into a Django web application can seem daunting if you do not know where to begin, but it is easier than you think if you use a developer tool such as Mapbox.
In this post we will build a simple Django project with a single app and add an interactive map like the one you see below to the webpage that Django renders with the Mapbox Maps API.
Python 3 is strongly recommended for this tutorial because Python 2 will no longer be supported starting January 1, 2020. Python 3.6.5 to was used to build this tutorial. We will also use the following application dependencies to build our application:
If you need help getting your development environment configured before running this code, take a look at this guide for setting up Python 3 and Django on Ubuntu 16.04 LTS.
This blog post's code is also available on GitHub within the maps-django-mapbox directory of the blog-code-examples repository. Take the code and use it for your own purposes because it is all provided under the MIT open source license.
Start the Django project by creating a new
virtual environment
using the following command. I recommend using a separate directory
such as ~/venvs/
(the tilde is a shortcut for your user's home
directory) so that you always know where all your virtualenvs are
located.
python3 -m venv djangomaps
Activate the virtualenv with the activate
shell script:
source djangomaps/bin/activate
The command prompt will change after activating the virtualenv:
Remember that you have to activate your virtualenv in every new terminal window where you want to use dependencies in the virtualenv.
We can now install the Django package into the activated but otherwise empty virtualenv.
pip install django==2.0.5
Look for the following output to confirm Django installed correctly from PyPI.
Downloading https://files.pythonhosted.org/packages/23/91/2245462e57798e9251de87c88b2b8f996d10ddcb68206a8a020561ef7bd3/Django-2.0.5-py3-none-any.whl (7.1MB)
100% |████████████████████████████████| 7.1MB 231kB/s
Collecting pytz (from django==2.0.5)
Using cached https://files.pythonhosted.org/packages/dc/83/15f7833b70d3e067ca91467ca245bae0f6fe56ddc7451aa0dc5606b120f2/pytz-2018.4-py2.py3-none-any.whl
Installing collected packages: pytz, django
Successfully installed django-2.0.5 pytz-2018.4
The Django dependency is ready to go so now we can create our project and add some awesome maps to the application.
We can use the Django django-admin.py
tool to create
the boilerplate code structure to get our project started.
Change into the directory where you develop your applications. For
example, I typically use /Users/matt/devel/py/
. Then run the following
command to start a Django project named djmaps
:
django-admin.py startproject djmaps
The django-admin.py
command will create a directory named djmaps
along
with several subdirectories that you should be familiar with if you have
previously worked with Django.
Change directories into the new project.
cd djmaps
Create a new Django app within djmaps
.
python manage.py startapp maps
Django will generate a new folder named maps
for the project.
We should update the URLs so the app is accessible before we write
our views.py
code.
Open djmaps/djmaps/urls.py
. Add the highlighted lines so that URLs
will check the maps
app for appropriate URL matching.
""" (comments)
"""
from django.conf.urls import include
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('', include('maps.urls')),
path('admin/', admin.site.urls),
]
Save djmaps/djmaps/urls.py
and open djmaps/djmaps/settings.py
.
Add the maps
app to settings.py
by inserting the highlighted line:
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'maps',
]
Make sure you change the default DEBUG
and SECRET_KEY
values in settings.py
before you deploy any code to production. Secure
your app properly with the information from the Django
production deployment checklist
so that you do not add your project to the list of hacked applications
on the web.
Save and close settings.py
.
Next change into the djmaps/maps
directory. Create a new file named
urls.py
to contain routes for the maps
app.
Add these lines to the empty djmaps/maps/urls.py
file.
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'', views.default_map, name="default"),
]
Save djmaps/maps/urls.py
and open djmaps/maps/views.py
add the
following two highlighted lines. You can keep the boilerplate comment or
delete it.
from django.shortcuts import render
def default_map(request):
return render(request, 'default.html', {})
Next, create a directory for your template files named templates
under
the djmaps/maps
app directory.
mkdir templates
Create a new file named default.html
within djmaps/maps/templates
that contains the following Django template markup.
<!DOCTYPE html>
<html>
<head>
<title>Interactive maps for Django web apps</title>
</head>
<body>
<h1>Map time!</h1>
</body>
</html>
We can test out this static page to make sure all of our code is
correct, then we'll use Mapbox to embed a customizable map within
the page. Change into the base directory of your Django project
where the manage.py
file is located. Execute the development
server with the following command:
python manage.py runserver
The Django development server will start up with no issues other than an unapplied migrations warning.
Performing system checks...
System check identified no issues (0 silenced).
You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
May 21, 2018 - 12:47:54
Django version 2.0.5, using settings 'djmaps.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Open a web browser and go to localhost:8000
.
Our code works, but boy is that a plain-looking HTML page. Let's make the magic happen by adding JavaScript to the template to generate maps.
Head to mapbox.com in your web browser to access the Mapbox homepage.
Click on "Get Started" or "Get Started for free" (the text depends on whether or not you already have a Mapbox account).
Sign up for a new free developer account or sign in to your existing account.
Click the "JS Web" option.
Choose "Use the Mapbox CDN" for the installation method. The next two screens
show some code that you should add to your djmaps/maps/templates/default.html
template file. The code will look like the following but you will need to
replace the mapboxgl.accessToken
line with your own access token.
<!DOCTYPE html>
<html>
<head>
<title>Interactive maps for Django web apps</title>
<script src='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css' rel='stylesheet' />
</head>
<body>
<h1>Map time!</h1>
<div id='map' width="100%" style='height:400px'></div>
<script>
mapboxgl.accessToken = {{ mapbox_access_token }};
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v10'
});
</script>
</body>
</html>
Re-open djmaps/maps/views.py
to update the parameters passed into the
Django template.
from django.shortcuts import render
def default_map(request):
# TODO: move this token to Django settings from an environment variable
# found in the Mapbox account settings and getting started instructions
# see https://www.mapbox.com/account/ under the "Access tokens" section
mapbox_access_token = 'pk.my_mapbox_access_token'
return render(request, 'default.html',
{ 'mapbox_access_token': mapbox_access_token })
The Mapbox access token should really be stored in the Django settings file, so we left a "TODO" note to handle that as a future step.
Now we can try our webpage again. Refresh localhost:8000
in your
web browser.
Sweet, we've got a live, interactive map! It's kind of weird thought how it is zoomed out to view the entire world. Time to customize the map using a few JavaScript parameters.
We can modify the map by changing parameters for the style, zoom level, location and many other attributes.
We'll start by changing the location that the initial map centers in on as well as the zoom level.
Re-open djmaps/maps/templates/default.html
and modify the first
highlighted lines so it ends with a commas and add the two new
highlighted lines shown below.
<!DOCTYPE html>
<html>
<head>
<title>Interactive maps for Django web apps</title>
<script src='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css' rel='stylesheet' />
</head>
<body>
<h1>Map time!</h1>
<div id='map' width="100%" style='height:400px'></div>
<script>
mapboxgl.accessToken = {{ mapbox_access_token }};
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v10',
center: [-77.03, 38.91],
zoom: 9
});
</script>
</body>
</html>
The first number, -77.03, for the center
array is the longitude
and the second number, 38.91, is the latitude. Zoom level 9 is much
closer to the city than the default which was the entire world at
level 0. All of the customization values are listed in the
Mapbox GL JS API documentation.
Now refresh the page at localhost:8000
to reload our map.
Awesome, now we are zoomed in on Washington, D.C. and can still move around to see more of the map. Let's make a couple other changes to our map before wrapping up.
Again back in djmaps/maps/templates/default.html
change the highlighted
line for the style
key to the mapbox://styles/mapbox/satellite-streets-v10
value. That will change the look from an abstract map style to satellite
image data. Update zoom: 9
so that it has a comma at the end of the line
and add bearing: 180
as the last key-value pair in the configuration.
<!DOCTYPE html>
<html>
<head>
<title>Interactive maps for Django web apps</title>
<script src='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css' rel='stylesheet' />
</head>
<body>
<h1>Map time!</h1>
<div id='map' width="100%" style='height:400px'></div>
<script>
mapboxgl.accessToken = {{ mapbox_access_token }};
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/satellite-streets-v10',
center: [-77.03, 38.91],
zoom: 9,
bearing: 180
});
</script>
</body>
</html>
Save the template and refresh localhost:8000
.
The map now provides a satellite view with streets overlay but it is
also... "upside down"! At least the map is upside down compared to how
most maps are drawn, due to the bearing: 180
value, which modified
this map's rotation.
Not bad for a few lines of JavaScript in our Django application. Remember to check the Mapbox GL JS API documentation for the exhaustive list of parameters that you can adjust.
We just learned how to add interactive JavaScript-based maps to our Django web applications, as well as modify the look and feel of the maps. Next try out some of the other APIs Mapbox provides including:
Questions? Let me know via a GitHub issue ticket on the Full Stack Python repository, on Twitter @fullstackpython or @mattmakai.
Do you see a typo, syntax issue or wording that's confusing in this blog post? Fork this page's source on GitHub and submit a pull request with a fix or file an issue ticket on GitHub.