In this Django tutorial, you will learn how to use Leaflet JS and GeoJson to display a map on an HTML page for every country. While this tutorial is specific to GeoDjango, the fundamentals of mapping with leafletjs are covered.
This blog post is a continuation from How to Use GeoDjango and accompanies the following video tutorial.
1. Default Sort Order
We can specify the default sort order to be alphabetic in our Country model definition at world/models.py
. Add the following Meta class.
... class Country(models.Model): ... class Meta: ordering = ['name']
2. Display a List of Countries
Make a folder at world/templates
and then a new file world/templates/index.html
with the following content that will display a list of countries.
<html> <body> <h1>Hello world</h1> {% for country in countries %} <p>{{ country.name }}</p> {% endfor %} </body> </html>
We need to define the countries variable and pass it into our template. Edit world/views.py
.
from django.shortcuts import render from world.models import Country def index(request): countries = Country.objects.all() context = {'countries': countries} return render(request, 'index.html', context)
Now we can tell Django about our new web page in world/urls.py
.
from django.contrib import admin from django.urls import path from world import views urlpatterns = [ path('admin/', admin.site.urls), path('home/', views.index), ]
Run the development server and you will see a list of countries.
2. Create a Page for Each Country
Let’s link each country to a country-specific page.
Tell Django about our country-specific web pages in world/urls.py
.
from django.contrib import admin from django.urls import path from world import views urlpatterns = [ path('admin/', admin.site.urls), path('home/', views.index), path('home/<str:sov_a3>', views.country), ]
The <str:sov_a3>
syntax simply means that when someone goes to the page for Afghanistan at http://localhost/home/AFG for example, the string “AFG” will be passed as an argument with the variable name sov_a3 to the country function in our world/views.py
file which we will create next.
from django.shortcuts import render from world.models import Country def index(request): countries = Country.objects.all() context = {'countries': countries} return render(request, 'index.html', context) def country(request, sov_a3): country = Country.objects.get(sov_a3=sov_a3) context = {'country': country} return render(request, 'country.html', context)
Notice how we do very similar things in the country and index functions. The difference is that we are returning all countries in the index function and returning only the country that matches the sov_a3 field in the country function.
Now we can create the actual HTML page for the countries with another Django template at world/templates/country.html
.
<html> <body> <h1>{{ country.name }}</h1> </body> </html>
Finally let’s link to the country pages with the sov_a3 code in world/templates/index.html
.
<html> <body> <h1>Hello world</h1> {% for country in countries %} <p><a href="{{ country.sov_a3 }}">{{ country.name }}<a></p> {% endfor %} </body> </html>
Testing this out turns the list of countries into hyperlinks.
Clicking on Afghanistan for example will show the page for Afghanistan like this.
3. Display a Map for Each Country
We will use the following Python packages.
- django-leaflet: to display maps
- django-geojson: to convert Django object to JSON format that Leaflet can understand
Install these packages with the pip package manager.
sudo apt update sudo apt install python3-pip pip install django-geojson django-leaflet
Add these to your Django apps list at gis/settings.py
.
... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.gis', 'world', 'leaflet', 'djgeojson', ] ...
Replace everything in world/templates/country.html
with the following.
{% load geojson_tags %} {% load leaflet_tags %} <html> <header> {% leaflet_js %} {% leaflet_css %} </header> <body> <h1>{{ country.name }}</h1> <div>{% leaflet_map "map" callback="map_init" %}</div> <script type="text/javascript"> function map_init(map, options) { var area = L.geoJson({{ country.geom|geojsonfeature|safe }}).addTo(map); map.fitBounds(area.getBounds()); } </script> </body> </html>
I will explain the above HTML. The first couple lines load the Django packages that we just installed (geojson_tags and leaflet_tags). In the header, we link to the Leaflet library with leaflet_js and leaflet_css. In the body, we define a div with a callback to the map_init function. The map_init JavaScript function is defined between the script tag on the next line, and this function takes the geom field from the Django country object that was passed in and converts it to a GeoJSON object called area which is added to the map. Finally, the fitBounds function uses the bounds of the map data to zoom into the country.
Testing this out for any country will display the map of that country as defined by the Natural Earth shapefile from the previous tutorial.