ModelForm inheritance for admin to input latitude and longitude in one field

Hi everyone,

Let’s say I have 2 models - Shop and its Category:

# myapp/models.py
from django.db import models


class Category(models.Model):
    name = models.CharField(max_length=30)  # e.g., "sports", "fashion"

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "categories"


class Shop(models.Model):
    name = models.CharField(max_length=30)  # e.g., "Joe's", "Rocky"
    latitude = models.DecimalField(max_digits=8, decimal_places=6)
    longitude = models.DecimalField(max_digits=9, decimal_places=6)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

A shop is described by its name, latitude and longitude values, and its category (which is implemented as a foreign key). Here we assume that each shop has one and only one category.

The thing is that most online maps allow me to copy latitude and longitude values as a comma-separated string, like -33.852694685484394, 151.22593913730648 (copied from Google Maps). I think it would be nice to be able to input latitude and longitude in this format on the admin page. To achieve that, I created an inherited class:

# myapp/forms.py
import re

from django import forms
from django.core.exceptions import ValidationError

from .models import Shop


class ShopForm(forms.ModelForm):

    # `lat_lon` is a char field for comma-separated pair of numbers
    lat_lon = forms.CharField(label="Latitude, longitude")

    class Meta:
        model = Shop
        fields = ["name", "lat_lon", "category"]

    def __init__(self, *args, **kwargs):
        super(ShopForm, self).__init__(*args, **kwargs)
        # Fill `lat_lon` field with the existing data (if any)
        if self.instance and self.instance.pk:
            self.fields["lat_lon"].initial = \
                f"{self.instance.latitude}, {self.instance.longitude}"

    def _get_pair(self):
        """Return two floats from `lat_lon` field."""
        return map(float, re.split(r",\s*", self.cleaned_data["lat_lon"]))

    def clean(self):
        # https://www.regular-expressions.info/floatingpoint.html
        number_pattern = r"[-+]?[0-9]*\.?[0-9]+"
        coords_pattern = number_pattern + r",\s*" + number_pattern
        # Check if `lat_lon` field contains two comma-separated numbers
        if not re.fullmatch(coords_pattern, self.cleaned_data["lat_lon"]):
            raise ValidationError("Wrong coordinates")
        # Check if these numbers are in the correct ranges
        num_1, num_2 = self._get_pair()
        if abs(num_1) > 90 or abs(num_2) > 180:
            raise ValidationError("Wrong coordinates")

    def save(self, commit=True):
        obj = super(ShopForm, self).save(commit=False)
        obj.latitude, obj.longitude = self._get_pair()
        if commit:
            obj.save()
        return obj

Finally, I have the following code for admin:

# myapp/admin.py
from django.contrib import admin

from .forms import ShopForm, Shop
from .models import Category


class ShopAdmin(admin.ModelAdmin):
    form = ShopForm


admin.site.register(Category)
admin.site.register(Shop, ShopAdmin)

All that works perfectly well. When I add a new shop or change an existing one, everything is fine:



However, I’m only a beginner in Django. Could you please tell me if I did all that in a correct way? Are there any important things here that I’m missing but I should be aware of?

Thank you.

Did you do anything “wrong”?
No

Are there any important things here you are missing?
No

Are there other ways of handling this?
Yes

Are those other ways “better”?
That would be an opinion.

Is it worth worrying about?
Probably not.

If what you have is working for you, then I’d set those concerns aside and keep moving forward.

One item you may wish to consider is that the conversion from a string to float is not necessarily precise. You may wish to convert directly to decimal. (Whether this is necessary depends upon your requirements for accuracy between what’s entered and what’s stored in the database.)

1 Like