GeoDjango and Leaflet.js- part two

This is the second post from GeoDjango i Leaflet.js series. You can find the previous post under this link.

After loading data to GeoDjango application now, it's time to present it to the user. You can use django template tag like {{object}} but I think it's better to provide api endpoints. I will be using GeoDjango builtin GeoJSON serializer. To do this declare new views in

from django.http import HttpResponse
from django.core.serializers import serialize
from .models import Point, Voivodeship

def points_view(request):
    points_as_geojson = serialize('geojson', Point.objects.all())
    return HttpResponse(points_as_geojson, content_type='json')

def wojewodztwa_view(request):
    voivodeships_as_geojson = serialize('geojson', Voivodeship.objects.all())
    return HttpResponse(voivodeships_as_geojson, content_type='json')

Also add following setting into

     "geojson": "django.contrib.gis.serializers.geojson",

GeoJSON is open format for encoding geographical data. It's based on JSON.

Then add lines to

from django.conf.urls import include, url
from django.contrib import admin
from voivodeships.views import points_view, voivodeships_view

urlpatterns = [
    url(r'^admin/', include(,
    url(r'^', points_view, name='points'),
    url(r'^', voivodeships_view, name='voivodeships'),

As you can see GeoDjango displays data from database in GeoJSON:

GeoJSON from GeoDjango

It's nice but end user need to see results on the map not in JSON format so I use Leaflet.js.

You can download leaflet.js from the web page but there is a better way: django-leaflet. It's django application with allows you embed leaflet to django project. Install it by:

$ pip install django-leaflet

Then make sure that leaflet is added to INSTALLED_APPS in

  # other applications

Let's add main page view to GeoDjango application in

from django.views.generic import TemplateView

class MainPageView(TemplateView):
    template_name = 'voivodeships/index.html'

And to

from voivodeships.views import MainPageView

urlpatterns = [# rest of urls
               url(r'^$', MainPageView.as_view()),]

After this add new index.html under voivodeships/templates/voivodeships/index.html with this content:

  {% load leaflet_tags %}
    {% leaflet_js %} {% leaflet_css %}
    {% leaflet_map "poland" %}

And going to the web page with running GeoDjango application you can see map:

Basic Leaflet.js map

Thanks to django-leaflet you can control behavior of all maps. Let add the following content to end of

  'DEFAULT_CENTER': (52.00,20.00),
  'MIN_ZOOM': 1,
  'MAX_ZOOM': 20,

But still map is not taking full space in the web page so let's add more CSS lines to fix that in index.html:

  <style media="screen">
    #poland {
      width: 100%;
      height: 100%;
  <!-- Rest of html -->

One of the Leaflet.js strong points is huge extensions database. In this project I will use few of them including: leaflet-ajax, leaflet-spin, markercluster. It's up to you how you want to install it. I will use bower for that:

$ bower install leaflet-ajax leaflet-spin leaflet.markerculster


    os.path.join(BASE_DIR, 'static'),

After installation got to index.html and use these plugins:

{% load static %}
  <!-- style tag and django-leaflet tag here -->
  <script src="{% static 'leaflet-ajax/dist/leaflet.ajax.min.js' %}"></script>
  <script src="{% static 'spin.js/spin.min.js' %}"></script>
  <script src="{% static 'leaflet-spin/leaflet.spin.js' %}"></script>
  <script type="text/javascript">
    function map_init_basic(map, options) {
      var geojsonPointLayer = new L.GeoJSON.AJAX("{% url 'points' %}", {
        onEachFeature: function (feature, layer) {

      var geojsonVoivodeshipsLayer = new L.GeoJSON.AJAX("{% url 'voivodeships' %}", {
        onEachFeature: function (feature, layer) {
  {% leaflet_map "poland" callback="window.map_init_basic" %}

I added new function map_init_basic which is a callback for django-leaflet tag. Then thanks to leaflet-ajax I get points and voivodeships GeoJSONs from GeoDjango. Moreover, I use function from leaflet.js: onEachFeature. This function add popup with the name of point or voivodeship.

There is one problem. GeoJSON with voivodeship is so accurate that deserializing takes a lot of time (about 41 sec). So one of the solution is to dump GeoJSON to cache, I will use Redis as a cache database.

First, install and check if Redis is working by:

$ sudo apt-get install redis-server
$ redis-cli ping PONG

Then it's time to install python bindings:

$ pip install redis
$ pip install django-redis-cache

After this adjust some settings in

    # ... another middlewares
    # ... rest of middlewares

    'default': {
        'BACKEND': 'redis_cache.RedisCache',
        'LOCATION': '',

What is important in MIDDLEWARE_CLASSES is order: UpdateCacheMiddleware should go before CommonMiddleware and FetchFromCacheMiddleware is supposed to be last.

Lastly, add cache to voivodeships_view in

from django.core.cache import cache

def voivodeships_view(request):
    redis_key = 'voivodeships'
    voivodeships = cache.get(redis_key)  # getting value for given key from redis
    if not voivodeships:
       voivodeships = serialize('geojson', Voivodeship.objects.all())
       cache.set(redis_key, voivodeships)  # if not GeoJSON is not in cache set it
    return HttpResponse(voivodeships, content_type='json')

Right now GeoJSON will be loaded from the database. After reloading the web page, django will get results from cache.

That's all: you have working GeoDjango application. The github repo is under this link

  • Update 28.06.18:

If you want your views to work with Django 2.0 you can use this snippet:

import json
from django.http import JsonResponse
from django.core.serializers import serialize
from .models import Point

def points_view(request):
    points_as_geojson = serialize( 'geojson',Point.objects.all())
    return JsonResponse(json.loads(points_as_geojson))

Thanks to Phyo Min Htwe for providing this piece of code!