Working with Geo🌎Django
Tags: django, geodjango, postgis, leaflet
Published: 2021-06-05 05:21:25

[GeoDjango](https://docs.djangoproject.com/en/3.2/ref/contrib/gis/tutorial/) is a built-in application that is included as a contrib module in Django. It’s actually a complete framework itself that can also be used separately from Django. It provides a toolbox of utilities for building GIS web applications.

### Purpose of this tutorial
Nothing much fancy, here we are getting co-ordinates(Longitude, Latitude) of location by inputting names of country and city.


## **Requirements** :
* [Postgres](https://www.postgresql.org/)
* [postgis](http://postgis.net/) extension need to be 
  installed on postgres
* [QGIS](https://www.qgis.org/en/site/) GeoDjango 
  Dependencies or install Geospatial libraries [GEOS, GDAL, and PROJ.4 ](https://docs.djangoproject.com/en/3.2/ref/contrib/gis/install/geolibs/),   

> Note: above requirements need to be install on your machine seperately before continuing

## Project setup:
```

$ mkdir dj_gis && cd dj_gis
$ python3 -m venv env
$ source env/bin/activate
$ pip install django djangorestframework django-leaflet geopy psycopg2-binary 
$ django-admin.py startproject config
$ python manage.py startapp location

```
**config/settings.py**
```
#config/settings.py

INSTALLED_APPS = [
    "django.contrib.gis",
    "location",
    "rest_framework",
]

```
**config/urls.py**
```
#config/urls.py

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


urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/v1/", include("location.urls")),
]

```
include **url.py** file on **location** app

```
#location/urls.py

from django.urls import path

urlpatterns = []
```
> So we finished basic setups

Let's create a model **location/models.py**
```
from django.db import models
from django.contrib.gis.db import models  # GeoDjango Model
from django.utils.translation import gettext_lazy as _


class Hotel(models.Model):
    name = models.CharField(max_length=255)
    street_1 = models.CharField(max_length=200)
    street_2 = models.CharField(max_length=200, null=True, blank=True)
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=100)
    zip_code = models.CharField(max_length=100)
    country = models.CharField(max_length=50)
    location = models.PointField(null=True)  # Spatial Field
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _("Hotel")
        verbose_name_plural = _("Hotels")

    def __str__(self):
        return f"{self.street_1}, {self.city}, {self.state}, {self.country}"

```
> here, we include a [pointfield](https://docs.djangoproject.com/en/3.2/ref/contrib/gis/model-api/) spatial field of geo django model api. 



let's create a **serializer** and **views** for our model.
```
# location/serializers.py

from location.models import Hotel
from rest_framework import serializers


class HotelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Hotel

        fields = (
            "id",
            "name",
            "street_1",
            "street_2",
            "city",
            "state",
            "zip_code",
            "country",
            "location",
        )

        extra_kwargs = {"location": {"read_only": True}}    
```

**location/views.py**
```
from location.serializers import HotelSerializer
from django.shortcuts import render
from rest_framework import generics
from .models import Hotel

from django.contrib.gis.geos import Point
from geopy.geocoders import Nominatim

geolocator = Nominatim(user_agent="location")


class ListCreateGenericViews(generics.ListCreateAPIView):
    queryset = Hotel.objects.all()
    serializer_class = HotelSerializer

    def perform_create(self, serializer):
        street_1 = serializer.initial_data["street_1"]
        address = serializer.initial_data["city"]
        state = serializer.initial_data["state"]
        country = serializer.initial_data["city"]
        data = [street_1, address, state, country]
        " ".join(data)

        g = geolocator.geocode(data)
        lat = g.latitude
        lng = g.longitude
        pnt = Point(lng, lat)
        print(pnt)
        serializer.save(location=pnt)


class HotelUpdateRetreiveView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Hotel.objects.all()
    serializer_class = HotelSerializer

    def perform_update(self, serializer):
        street_1 = serializer.initial_data["street_1"]
        address = serializer.initial_data["city"]
        state = serializer.initial_data["state"]
        country = serializer.initial_data["city"]
        data = [street_1, address, state, country]
        " ".join(data)

        g = geolocator.geocode(data)
        lat = g.latitude
        lng = g.longitude
        pnt = Point(lng, lat)
        print(pnt)
        serializer.save(location=pnt)

```

> Note: above **view** can be further refactor using **viewsets** or your desired ones.

so, [geopy](https://geopy.readthedocs.io/en/stable/) library comes in handy, which is  is a Python client for several popular geocoding web services. geopy makes it easy for Python developers to locate the coordinates of addresses, cities, countries, and landmarks across the globe using third-party geocoders and other data sources.

let's update our urls:
```
#location/urls.py

from django.urls import path
from .views import HotelUpdateRetreiveView, ListCreateGenericViews

urlpatterns = [
    path("hotels", ListCreateGenericViews.as_view()),
    path(
        "hotels/<str:pk>",
        HotelUpdateRetreiveView.as_view(),
    ),
]
```

Creating a Database:
```
sudo -u postgres psql

CREATE DATABASE locator;

CREATE USER locator WITH PASSWORD 'locator';

CREATE EXTENSION postgis;

ALTER ROLE locator SET client_encoding TO 'utf8';

ALTER ROLE locator SET default_transaction_isolation TO 'read committed';

ALTER ROLE locator SET timezone TO 'UTC';

ALTER ROLE locator SUPERUSER;

GRANT ALL PRIVILEGES ON DATABASE locator TO locator;
```
let's make some changes on our **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",
    "location",
    "rest_framework",
    "leaflet",
]

DATABASES = {
    "default": {
        "ENGINE": "django.contrib.gis.db.backends.postgis",
        "NAME": "locator",
        "USER": "locator",
        "PASSWORD": "locator",
        "HOST": "localhost",
        "PORT": "5432",
    }
}

LEAFLET_CONFIG = {
    # "SPATIAL_EXTENT": (5.0, 44.0, 7.5, 46),
    "DEFAULT_CENTER": (13.3888599 52.5170365), #set your corordinate
    "DEFAULT_ZOOM": 16,
    "MIN_ZOOM": 3,
    "MAX_ZOOM": 20,
    "DEFAULT_PRECISION": 6,
    "SCALE": "both",
    "ATTRIBUTION_PREFIX": "powered by me",
}

```

Registering model on admin
```
from django.contrib import admin
from leaflet.admin import LeafletGeoAdmin

from .models import Hotel


@admin.register(Hotel)
class HotelAdmin(LeafletGeoAdmin):
    list_display = ("id", "name", "location", "created_at", "updated_at")
```

> so, we have added leaflet, leaflet_config and database.
For more about Leaflet you can visit [Read the docs](https://django-leaflet.readthedocs.io/en/latest/templates.html)

let's run our app:
```
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
```

here, what you get on admin panel.
![admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dfjvq878bzcl4xyrcvc.png)

Let's add some data using browsable api or you can use postman too.
> Try it out yourself for udate and delete

![browsable api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebhyxz09kjua2zif09r9.png)


You can find the code in github repo.
{% github lyamaa/dj_gis no-readme %}