Saving GeoPoints with Django Form

Gaurav Jain 🏆
4 min readDec 24, 2023

A while ago I was working on a project where I integrated a map into a straightforward model form. Users could select a point on the map and submit it. The form’s task was to receive the data, validate it, and if all was well, store it in the database. I utilized MySQL with GIS support for this task. Along the way, I encountered a few hurdles that I’ll deep dive into here, explaining how I resolved them.

Alright, Let’s begin. Consider the below example

from django.contrib.gis.db import models
class Location(models.Model):
coordinate = models.PointField(blank=True, null=True)
# ... many more fields

If you examine the Django-generated migration file for this model, you’ll observe that the default value of the srid parameter is set to 4326, even though we never explicitly provided that in the model definition.

operations = [   
migrations.CreateModel(
name='Location',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('coordinate', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326)),
],
),
]

The default value of srid is being propagated from the base classBaseSpatialField, the parent class of PointField. While we have the flexibility to adjust this value based on our needs, the default usually serves well for most cases.

Let’s try to save some Geo coordinates through the shell. To get started, let’s import the Point class so that we can directly assign the value to the model field. Go ahead and fire up the Python shell using python manage.py shell .

>>> from django.contrib.gis.geos import Point
>>> Point(75.778885, 26.922070) # Latitude=26.922070 & longitude=75.778885
<Point object at 0x11a282c70>
>>> # save into database
>>> Location.objects.create(coordinate=Point(75.778885, 26.922070))
<Location: Location object (1)>

Let's see how it’s been stored in the database.

>>> Location.objects.last().coordinate.coords
(75.778885, 26.92207)

Looks good. That's what we entered.

Let's do the same exercise using the Django model form. Create a forms.py file as below

from django.contrib.gis import forms  
# Note: forms is being imported from gis module instead of
# `from django import forms`

class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ('coordinate',)

Next, let’s pass this data to the form and observe its response.

>>> data = {'coordinate': '75.778885, 26.92207'}
>>> form = LocationForm(data=data)
>>> form
<LocationForm bound=True, valid=Unknown, fields=(coordinate)>
>>> form.is_valid()
Error creating geometry from value '75.778885, 26.92207' (String input unrecognized as WKT EWKT, and HEXEWKB.)

Oops! We got the below error

Error creating geometry from value ‘75.778885, 26.92207’ (String input unrecognized as WKT EWKT, and HEXEWKB.)

It seems the data we provided is not in one of the acceptable formats. Here we need to provide a proper geometry type with the data.

>>> data = {'coordinate': 'POINT(75.778885 26.92207)'}
>>> form = LocationForm(data=data)
>>> form.is_valid()
True

Nice, It worked! But wait… Did it really? Too soon to celebrate 😏. Let’s save this form and verify the data in the database.

>>> form.save()
<Location: Location object (2)
>>> Location.objects.last().coordinate.coords
(0.0006807333060903553, 0.0002418450696118364)

Whaaaaaaat?

This is not what we provided.

What went wrong? Seems like Django forms require the srid value explicitly. Let's tweak the data and go through the same steps again.

>>> data = {'coordinate': 'SRID=4326;POINT(75.778885 26.92207)'}
>>> form = LocationForm(data=data)
>>> form.is_valid()
True
>>> form.save()
<Location: Location object (3)>

Verify the database.

>>> Location.objects.last().coordinate.coords
(75.778885, 26.92207)
>>>

Fantastic! At last, our inserted data is visible. Victory ✌️!

The question, now, is where and how should we implement this change in the codebase.

We’ve got a couple of options on the table here:

  1. Tweaking the payload before feeding it into the form. Not the best spot for it, especially if we’re using this form across multiple sections. That brings us to the second choice.
  2. Overriding the __init__ method within the Form class, consolidating all the logic into one central place.
class LocationForm(forms.ModelForm):    
class Meta:
model = Location
fields = ('coordinate',)

def __init__(self, *args, **kwargs):
coordinate = kwargs['data'].pop('coordinate', None)
if coordinate:
# remove comma, as we need single space between two numbers for Point().
coordinate = coordinate.replace(',', '')
kwargs['data']['coordinate'] = f'SRID=4326;POINT({coordinate})' super(LocationForm, self).__init__(*args, **kwargs)

Now we don’t need to pass Geometry Type(GEOM_TYPE) and SRID in the data. we can simply pass the raw point data as we did in the very first step.

>>> data = {'coordinate': '75.778885, 26.92207'}
>>> form = LocationForm(data=data)
>>> form.save()
<Location: Location object (4)>

Verify the database.

>>> Location.objects.last().coordinate.coords
(75.778885, 26.92207)
>>>

👏👏👏 Sweet!

Additionally, for extra conditional checks aligned with your business logic, consider overriding the clean_<field_name> or/and clean method(s). Place all the logic there and trigger relevant exceptions or validation errors as required. Also, if your model contains multiple Point fields, consider creating a method within the class to reuse within the __init__ method to follow DRY.

For more such articles, follow me at: https://gauravvjn.medium.com.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Gaurav Jain 🏆
Gaurav Jain 🏆

Written by Gaurav Jain 🏆

Experienced Python dev, building secure & scalable APIs. If you are seeking short term or long term support with your Python project? Leave a private note!

No responses yet

Write a response