How to integrate a map in a Django app using Folium.

How to integrate a map in a Django app using Folium.

I always found fascinating a map app. The way you can find any place just by inserting the data and almost at once, you have the place's address. But, what I going to show you is an app more simple, using django framework and folium library.

pip install django
pip install folium
django-admin startproject maps_project
py manage.py startapp maps

The next step is go to setting.py and write the name of the app in 'INSTALLED_APPS':

 INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'maps',
]

In the project directory, write this code in urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
   path('',include('maps.urls')),
   path('admin/', admin.site.urls),
 ]

The next step is to create the models of our app, it is necessary one class and two fields, latitude and longitude. We need to go to the app directory and in models.py write this code:

 class Coordenadas(models.Model):

       lat = models.FloatField(max_length=20)
       lon = models.FloatField(max_length=20)

Now we need to register our model in admin.py:

  from django.contrib import admin
  from .models import *


  admin.site.register(Coordenadas)

I like to create a forms.py to use ModelForm feature of django, it is simple, just create a new file and name it 'forms.py':

 from django import forms
 from django.forms import ModelForm

 from .models import *

 class CoordinatesForm(forms.ModelForm):
      lat = models.CharField(max_length=20)
      lon = models.CharField(max_length=20)

class Meta:
    model = Coordenadas
    fields = '__all__'

I will show views.py first and then explained it.

from django.shortcuts import render, redirect
from .models import Coordenadas
from .forms import *
import folium


def coordinates_form(request):
   coordinates = Coordenadas.objects.all()
   form = CoordinatesForm()

   if request.method == 'POST':
      form = CoordinatesForm(request.POST)
       if form.is_valid():
           form.save()
       return redirect("maps")
      context = {
        'coordinates': coordinates,
        'form' : form,
       }
  return render(request, 'maps/maps_form.html', context)

def maps(request):
   coordenadas = list(Coordenadas.objects.values_list('lat','lon'))[-1]

   map = folium.Map(coordenadas)
   folium.Marker(coordenadas).add_to(map)
   folium.raster_layers.TileLayer('Stamen Terrain').add_to(map)
   folium.raster_layers.TileLayer('Stamen Toner').add_to(map)
   folium.raster_layers.TileLayer('Stamen Watercolor').add_to(map)
   folium.LayerControl().add_to(map)


   map = map._repr_html_()
   context = {
      'map': map,

    }
  return render(request, 'maps/maps.html', context)

'coordinates_form' handles all the things related to our form. We make a variable 'coordinates' and assign all the objects from our model. And in the variable 'form' we assign the form from 'forms.py'. If the server receives a POST request. And the input data of our form is valid, the server saves it, and redirects the user to the map's page.

To the variable context, we assign all the objects of our models and form that will be rendering to our form page.

In 'maps' we have a variable 'map' to display a map using folium library function called 'Map'. This function has 'location' as a first parameter. And to be valid, this parameter has to be a list or a tuple, like this [9.7334, -63.1918] or (9.7334, -63.1918). 'Coordanadas.objects' is neither a list nor a tuple, we have to transform it.

'value_list' is a method inside django to transform queryset objects into a list. But only with that method do we have "Queryset [(1, 9.7334, -63.1918)] ". Inside 'value_list' we can select the elements we want 'lat' and 'lon'. Then we wrap it inside list() and we '[( 9.7334, -63.1918)]', still not a valid parameter, this is a list with one element inside, a tuple. We extract this element with [-1], instead of [0]. If use [0] we will get the first element no matter the size of the list. If you want a map of Berlin, and then Paris, you won't get the map of Paris you will get Berlin instead. Because it is the first query.

 print(Coordenadas.objects.values_list('lat', 'lon'))   #result: <Queryset [(1, 9.7334, -63.1918)] >

 print(list(Coordenadas.object.values.list('lat', 'lon')))   #result: [( 9.7334, -63.1918)]

 print(list(Coordenadas.object.values.list('lat', 'lon'))[-1])   #result: ( 9.7334, -63.1918)

Then we transform our map into an HTML file with ._reprhtml(), we assign it to context and then render it to 'maps.html'. 'folium.Marker()' is a function to mark our location and 'folium.LayerControl()' is a function to display a button to control different layers. For more information about layers and functions of folium, please go to the documentation.

urls.py

 from django.contrib import admin
 from django.urls import path
 from . import views

 urlpatterns = [
     path('',views.coordinates_form, name = 'coordinates-form'),
     path('map', views.maps, name = 'maps'),
  ]

maps.html

      <!DOCTYPE html>
      <html lang="en">
      {% block content %}
      <head>
          <meta charset="UTF-8">
          <title>Map</title>
       </head>
       <body>
           <div> {{ map | safe }}</div>

        </body>
        </html>

       {% endblock %}

base.html

maps_form.html

This is the source code.

If you have any recommendations about other packages, how to improve my code, my English, anything; please leave a comment or contact me through Twitter or LinkedIn.

form.jpg

maps.png

References: