|
| 1 | +title: How to Add Maps to Django Web App Projects with Mapbox |
| 2 | +slug: maps-django-web-applications-projects-mapbox |
| 3 | +meta: Learn how to add maps and location-based data to your web applications using Mapbox. |
| 4 | +category: post |
| 5 | +date: 2018-05-19 |
| 6 | +modified: 2018-05-21 |
| 7 | +newsletter: False |
| 8 | +headerimage: /img/180519-django-maps/header.jpg |
| 9 | +headeralt: Python, Django and Mapbox logos are copyright their respective owners. |
| 10 | + |
| 11 | + |
| 12 | +Building interactive maps into a [Django](/django.html) web application |
| 13 | +can seem daunting if you do not know where to begin, but it is easier |
| 14 | +than you think if you use a developer tool such as |
| 15 | +[Mapbox](https://www.mapbox.com/). |
| 16 | + |
| 17 | +<div id='map' width="100%" style='height:280px;margin-bottom:16px' class="shot rnd outl"></div> |
| 18 | +<script> |
| 19 | +mapboxgl.accessToken = 'pk.eyJ1IjoibWF0dG1ha2FpIiwiYSI6ImNqZzU0OXNtYjIzdmIyeHA5OG1sNnhid2YifQ.kzJityumPUk9f9i1vkmWAg'; |
| 20 | +var map = new mapboxgl.Map({ |
| 21 | + container: 'map', |
| 22 | + style: 'mapbox://styles/mapbox/streets-v10', |
| 23 | + center: [-77.03, 38.91], |
| 24 | + zoom: 9 |
| 25 | +}); |
| 26 | +</script> |
| 27 | + |
| 28 | +In this post we will build a simple Django project with a single app |
| 29 | +and add an interactive map like the one you see above to the webpage that |
| 30 | +Django renders with the [Mapbox Maps](https://www.mapbox.com/maps/) |
| 31 | +[API](/application-programming-interfaces.html). |
| 32 | + |
| 33 | + |
| 34 | +## Our Tools |
| 35 | +[Python 3](/python-2-or-3.html) is strongly recommended for this tutorial |
| 36 | +because Python 2 will no longer be supported starting January 1, 2020. |
| 37 | +[Python 3.6.5](https://www.python.org/downloads/release/python-365/) to |
| 38 | +was used to build this tutorial. We will also use the following |
| 39 | +[application dependencies](/application-dependencies.html) to build |
| 40 | +our application: |
| 41 | + |
| 42 | +* [Django](/django.html) web framework, |
| 43 | + [version 2.0.5](https://docs.djangoproject.com/en/2.0/) |
| 44 | +* [pip](https://pip.pypa.io/en/stable/) and |
| 45 | + [virtualenv](https://virtualenv.pypa.io/en/latest/), which come installed |
| 46 | + with Python 3, to install and isolate these Django and Rollbar libraries |
| 47 | + from your other applications |
| 48 | +* A [free Mapbox account](https://www.mapbox.com/) to interact with their |
| 49 | + [web API](/application-programming-interfaces.html) using |
| 50 | + [JavaScript](/javascript.html) |
| 51 | + |
| 52 | +If you need help getting your |
| 53 | +[development environment](/development-environments.html) configured |
| 54 | +before running this code, take a look at |
| 55 | +[this guide for setting up Python 3 and Django on Ubuntu 16.04 LTS](/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html). |
| 56 | + |
| 57 | +This blog post's code is also available on GitHub within the |
| 58 | +[maps-django-mapbox directory of the blog-code-examples repository](https://github.com/fullstackpython/blog-code-examples). |
| 59 | +Take the code and use it for your own purposes because it is all |
| 60 | +provided under the MIT open source license. |
| 61 | + |
| 62 | + |
| 63 | +## Installing Dependencies |
| 64 | +Start the Django project by creating a new |
| 65 | +[virtual environment](/virtual-environments-virtualenvs-venvs.html) |
| 66 | +using the following command. I recommend using a separate directory |
| 67 | +such as `~/venvs/` (the tilde is a shortcut for your user's `home` |
| 68 | +directory) so that you always know where all your virtualenvs are |
| 69 | +located. |
| 70 | + |
| 71 | +```bash |
| 72 | +python3 -m venv djangomaps |
| 73 | +``` |
| 74 | + |
| 75 | +Activate the virtualenv with the `activate` shell script: |
| 76 | + |
| 77 | +```bash |
| 78 | +source djangomaps/bin/activate |
| 79 | +``` |
| 80 | + |
| 81 | +The command prompt will change after activating the virtualenv: |
| 82 | + |
| 83 | +<img src="/img/180519-django-maps/virtualenv.jpg" width="100%" class="shot rnd outl" alt="Activate your djangomaps virtualenv."> |
| 84 | + |
| 85 | +Remember that you have to activate your virtualenv in every new terminal |
| 86 | +window where you want to use dependencies in the virtualenv. |
| 87 | + |
| 88 | +We can now install the [Django](https://pypi.python.org/pypi/Django/2.0.5) |
| 89 | +package into the activated but otherwise empty virtualenv. |
| 90 | + |
| 91 | +``` |
| 92 | +pip install django==2.0.5 |
| 93 | +``` |
| 94 | + |
| 95 | +Look for the following output to confirm Django installed |
| 96 | +correctly from PyPI. |
| 97 | + |
| 98 | +``` |
| 99 | + Downloading https://files.pythonhosted.org/packages/23/91/2245462e57798e9251de87c88b2b8f996d10ddcb68206a8a020561ef7bd3/Django-2.0.5-py3-none-any.whl (7.1MB) |
| 100 | + 100% |████████████████████████████████| 7.1MB 231kB/s |
| 101 | + Collecting pytz (from django==2.0.5) |
| 102 | + Using cached https://files.pythonhosted.org/packages/dc/83/15f7833b70d3e067ca91467ca245bae0f6fe56ddc7451aa0dc5606b120f2/pytz-2018.4-py2.py3-none-any.whl |
| 103 | + Installing collected packages: pytz, django |
| 104 | + Successfully installed django-2.0.5 pytz-2018.4 |
| 105 | +``` |
| 106 | + |
| 107 | +The Django dependency is ready to go so now we can create our project |
| 108 | +and add some awesome maps to the application. |
| 109 | + |
| 110 | + |
| 111 | +## Building Our Django Project |
| 112 | +We can use the [Django](/django.html) `django-admin.py` tool to create |
| 113 | +the boilerplate code structure to get our project started. |
| 114 | +Change into the directory where you develop your applications. For |
| 115 | +example, I typically use `/Users/matt/devel/py/`. Then run the following |
| 116 | +command to start a Django project named `djmaps`: |
| 117 | + |
| 118 | +``` |
| 119 | +django-admin.py startproject djmaps |
| 120 | +``` |
| 121 | + |
| 122 | +The `django-admin.py` command will create a directory named `djmaps` along |
| 123 | +with several subdirectories that you should be familiar with if you have |
| 124 | +previously worked with Django. |
| 125 | + |
| 126 | +Change directories into the new project. |
| 127 | + |
| 128 | +``` |
| 129 | +cd djmaps |
| 130 | +``` |
| 131 | + |
| 132 | +Create a new Django app within `djmaps`. |
| 133 | + |
| 134 | +``` |
| 135 | +python manage.py startapp maps |
| 136 | +``` |
| 137 | + |
| 138 | +Django will generate a new folder named `maps` for the project. |
| 139 | +We should update the URLs so the app is accessible before we write |
| 140 | +our `views.py` code. |
| 141 | + |
| 142 | +Open `djmaps/djmaps/urls.py`. Add the highlighted lines so that URLs |
| 143 | +will check the `maps` app for appropriate URL matching. |
| 144 | + |
| 145 | +```python |
| 146 | +""" (comments) |
| 147 | +""" |
| 148 | +~~from django.conf.urls import include |
| 149 | +from django.contrib import admin |
| 150 | +from django.urls import path |
| 151 | + |
| 152 | + |
| 153 | +urlpatterns = [ |
| 154 | +~~ path('', include('maps.urls')), |
| 155 | + path('admin/', admin.site.urls), |
| 156 | +] |
| 157 | +``` |
| 158 | + |
| 159 | +Save `djmaps/djmaps/urls.py` and open `djmaps/djmaps/settings.py`. |
| 160 | +Add the `maps` app to `settings.py` by inserting the highlighted line: |
| 161 | + |
| 162 | +```python |
| 163 | +# Application definition |
| 164 | + |
| 165 | +INSTALLED_APPS = [ |
| 166 | + 'django.contrib.admin', |
| 167 | + 'django.contrib.auth', |
| 168 | + 'django.contrib.contenttypes', |
| 169 | + 'django.contrib.sessions', |
| 170 | + 'django.contrib.messages', |
| 171 | + 'django.contrib.staticfiles', |
| 172 | +~~ 'maps', |
| 173 | +] |
| 174 | +``` |
| 175 | + |
| 176 | +Make sure you change the default `DEBUG` and `SECRET_KEY` |
| 177 | +values in `settings.py` before you deploy any code to production. Secure |
| 178 | +your app properly with the information from the Django |
| 179 | +[production deployment checklist](https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/) |
| 180 | +so that you do not add your project to the list of hacked applications |
| 181 | +on the web. |
| 182 | + |
| 183 | +Save and close `settings.py`. |
| 184 | + |
| 185 | +Next change into the `djmaps/maps` directory. Create a new file named |
| 186 | +`urls.py` to contain routes for the `maps` app. |
| 187 | + |
| 188 | +Add these lines to the empty `djmaps/maps/urls.py` file. |
| 189 | + |
| 190 | +```python |
| 191 | +from django.conf.urls import url |
| 192 | +from . import views |
| 193 | + |
| 194 | +urlpatterns = [ |
| 195 | + url(r'', views.default_map, name="default"), |
| 196 | +] |
| 197 | +``` |
| 198 | + |
| 199 | +Save `djmaps/maps/urls.py` and open `djmaps/maps/views.py` add the |
| 200 | +following two highlighted lines. You can keep the boilerplate comment or |
| 201 | +delete it. |
| 202 | + |
| 203 | +```python |
| 204 | +from django.shortcuts import render |
| 205 | + |
| 206 | + |
| 207 | +~~def default_map(request): |
| 208 | +~~ return render(request, 'default.html', {}) |
| 209 | +``` |
| 210 | + |
| 211 | +Next, create a directory for your template files named `templates` under |
| 212 | +the `djmaps/maps` app directory. |
| 213 | + |
| 214 | +``` |
| 215 | +mkdir templates |
| 216 | +``` |
| 217 | + |
| 218 | +Create a new file named `default.html` within `djmaps/maps/templates` |
| 219 | +that contains the following [Django template](/django-templates.html) markup. |
| 220 | + |
| 221 | +``` |
| 222 | +<!DOCTYPE html> |
| 223 | +<html> |
| 224 | + <head> |
| 225 | + <title>Interactive maps for Django web apps</title> |
| 226 | + </head> |
| 227 | + <body> |
| 228 | + <h1>Map time!</h1> |
| 229 | + </body> |
| 230 | +</html> |
| 231 | +``` |
| 232 | + |
| 233 | +We can test out this static page to make sure all of our code is |
| 234 | +correct, then we'll use Mapbox to embed a customizable map within |
| 235 | +the page. Change into the base directory of your Django project |
| 236 | +where the `manage.py` file is located. Execute the development |
| 237 | +server with the following command: |
| 238 | + |
| 239 | +```bash |
| 240 | +python manage.py runserver |
| 241 | +``` |
| 242 | + |
| 243 | +The Django development server will start up with no issues other than an |
| 244 | +unapplied migrations warning. |
| 245 | + |
| 246 | +``` |
| 247 | +Performing system checks... |
| 248 | +
|
| 249 | +System check identified no issues (0 silenced). |
| 250 | +
|
| 251 | +You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. |
| 252 | +Run 'python manage.py migrate' to apply them. |
| 253 | +
|
| 254 | +May 21, 2018 - 12:47:54 |
| 255 | +Django version 2.0.5, using settings 'djmaps.settings' |
| 256 | +Starting development server at http://127.0.0.1:8000/ |
| 257 | +Quit the server with CONTROL-C. |
| 258 | +``` |
| 259 | + |
| 260 | +Open a web browser and go to `localhost:8000`. |
| 261 | + |
| 262 | +<img src="/img/180519-django-maps/map-time.png" width="100%" class="shot rnd outl" alt="Plain old HTML page."> |
| 263 | + |
| 264 | +Our code works, but boy is that a plain-looking HTML page. Let's make the |
| 265 | +magic happen by adding JavaScript to the template to generate maps. |
| 266 | + |
| 267 | + |
| 268 | +## Adding Maps with Mapbox |
| 269 | +Head to [mapbox.com](https://www.mapbox.com/) in your web browser to |
| 270 | +access the Mapbox homepage. |
| 271 | + |
| 272 | +<img src="/img/180519-django-maps/mapbox-homepage.jpg" width="100%" class="shot rnd outl" alt="Mapbox homepage."> |
| 273 | + |
| 274 | +Click on "Get Started" or "Get Started for free" (the text depends on whether |
| 275 | +or not you already have a Mapbox account). |
| 276 | + |
| 277 | +<img src="/img/180519-django-maps/sign-up.jpg" width="100%" class="shot rnd outl" alt="Sign up for a Mapbox account."> |
| 278 | + |
| 279 | +Sign up for a new free developer account or sign in to your existing |
| 280 | +account. |
| 281 | + |
| 282 | +<img src="/img/180519-django-maps/add-mapbox.png" width="100%" class="shot rnd outl" alt="Add Mapbox to your application."> |
| 283 | + |
| 284 | +Click the "JS Web" option. |
| 285 | + |
| 286 | +<img src="/img/180519-django-maps/method-installation.png" width="100%" class="shot rnd outl" alt="Choose the method of installation."> |
| 287 | + |
| 288 | +Choose "Use the Mapbox CDN" for the installation method. The next two screens |
| 289 | +show some code that you should add to your `djmaps/maps/templates/default.html` |
| 290 | +template file. The code will look like the following but you will need to |
| 291 | +replace the `mapboxgl.accessToken` line with your own access token. |
| 292 | + |
| 293 | +``` |
| 294 | +<!DOCTYPE html> |
| 295 | +<html> |
| 296 | + <head> |
| 297 | + <title>Interactive maps for Django web apps</title> |
| 298 | +~~ <script src='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js'></script> |
| 299 | +~~ <link href='https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css' rel='stylesheet' /> |
| 300 | + </head> |
| 301 | + <body> |
| 302 | + <h1>Map time!</h1> |
| 303 | +~~ <div id='map' width="100%" style='height:400px'></div> |
| 304 | +~~ <script> |
| 305 | +~~ mapboxgl.accessToken = {{ mapbox_access_token }}; |
| 306 | +~~ var map = new mapboxgl.Map({ |
| 307 | +~~ container: 'map', |
| 308 | +~~ style: 'mapbox://styles/mapbox/streets-v10' |
| 309 | +~~ }); |
| 310 | +~~ </script> |
| 311 | + </body> |
| 312 | +</html> |
| 313 | +``` |
| 314 | + |
| 315 | +Re-open `djmaps/maps/views.py` to update the parameters passed into the |
| 316 | +Django template. |
| 317 | + |
| 318 | +```python |
| 319 | +from django.shortcuts import render |
| 320 | + |
| 321 | + |
| 322 | +def default_map(request): |
| 323 | +~~ # TODO: move this token to Django settings from an environment variable |
| 324 | +~~ # found in the Mapbox dashboard and getting started instructions |
| 325 | +~~ mapbox_access_token = 'pk.my_mapbox_access_token' |
| 326 | +~~ return render(request, 'default.html', |
| 327 | +~~ {'mapbox_access_token':mapbox_access_token}) |
| 328 | +``` |
| 329 | + |
| 330 | +The Mapbox access token should really be stored in the Django settings |
| 331 | +file, so we left a TODO note to handle that as a future step. |
| 332 | + |
| 333 | +Now we can try our webpage again. Refresh `localhost:8000` in your |
| 334 | +web browser. |
| 335 | + |
| 336 | +<img src="/img/180519-django-maps/map-time-with-map.png" width="100%" class="shot rnd outl" alt="Screenshot of the Mapbox map showing up in our Django front end."> |
| 337 | + |
| 338 | +Sweet, we've got a live, interactive map! It's kind of weird thought how it |
| 339 | +is zoomed out to view the entire world. Time to customize the map using |
| 340 | +a few JavaScript parameters. |
| 341 | + |
| 342 | + |
| 343 | +## Customizing the Map |
| 344 | +We can modify the map by changing parameters for the style, zoom level, |
| 345 | +location and many other attributes. |
| 346 | + |
| 347 | + |
| 348 | + |
| 349 | +## What's Next? |
| 350 | +We just learned how to add interactive JavaScript-based maps to our |
| 351 | +[Django](/django.html) web applications, as well as modify the look |
| 352 | +and feel of the maps. Next try out some of the other APIs Mapbox |
| 353 | +provides including: |
| 354 | + |
| 355 | +* [directions](https://www.mapbox.com/api-documentation/#directions) |
| 356 | +* [map matching](https://www.mapbox.com/api-documentation/#map-matching) |
| 357 | +* [geocoding](https://www.mapbox.com/api-documentation/#geocoding) |
| 358 | + |
| 359 | +Questions? Let me know via |
| 360 | +[a GitHub issue ticket on the Full Stack Python repository](https://github.com/mattmakai/fullstackpython.com/issues), |
| 361 | +on Twitter |
| 362 | +[@fullstackpython](https://twitter.com/fullstackpython) |
| 363 | +or [@mattmakai](https://twitter.com/mattmakai). |
| 364 | + |
| 365 | +Do you see a typo, syntax issue or wording that's confusing in this blog |
| 366 | +post? Fork |
| 367 | +[this page's source on GitHub](https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180519-django-maps-mapbox.markdown) |
| 368 | +and submit a pull request with a fix or |
| 369 | +[file an issue ticket on GitHub](https://github.com/mattmakai/fullstackpython.com/issues). |
0 commit comments