Search code examples
djangoformscoordinatespointgeodjango

GeoDjango saving Point geometry from form


I have one form and one formset in my django project. The form has one input with a Point geometry. So I have input there which looks something like that:

39.237103, 25.667217

when user sends a form I want to split this input and save this as Point geometry, in models Point looks like this:

position = gismodels.PointField(null=True, srid=4326)

I use this code for validation and saving form, formset and Point geometry

if request.method == "POST":
    checklist_form = ChecklistForm(request.POST)
    observation_formset = ObservationFormSet(request.POST)

    #error
    if checklist_form.is_valid() and observation_formset.is_valid():
        checklist = checklist_form.save(commit=False)
        latitude, longitude = request.POST.get('position', '').split(', ', 1)
        checklist.position = Point(longitude, latitude)
        checklist.save()
        for observation_form in observation_formset:
            observation = observation_form.save(commit=False)
            observation.checklist_id = checklist
            observation.save()

But the problem is that POST data for position has bad format so validation of checklist_form raise this error before I can split the coordinates:

String or unicode input unrecognized as WKT EWKT, and HEXEWKB.

I read that I can copy POST data and change them, but I also read it is a bad practice. What I think about is using javascript for changing coordinates for appropriate format but GeoDjango surely has better functionality for saving Point geometry.


Solution

  • Here is a simple field for entering or editing Points using django:

    import re
    from django.contrib.gis import forms
    from django.contrib.gis.geos import Point
    from django.core.exceptions import ValidationError
    from django.utils.encoding import force_text
    from django.utils.translation import ugettext_lazy as _
    
    
    class SimplePointField(forms.Field):
        default_error_messages = {
            'invalid': _('Enter latitude,longitude'),
        }
        re_point = re.compile(r'^\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\s*$')
    
        def prepare_value(self, value):
            if isinstance(value, Point):
                return "{},{}".format(*value.coords)
            return value
    
        def to_python(self, value):
            """
            Validates input. Returns a Point instance or None for empty values.
            """
            value = super(SimplePointField, self).to_python(value)
            if value in self.empty_values:
                return None
            try:
                m = self.re_point.match(force_text(value))
                if not m:
                    raise ValueError()
                value = Point(float(m.group(1)), float(m.group(2)))
            except (ValueError, TypeError):
                raise ValidationError(self.error_messages['invalid'],
                                      code='invalid')
    
            return value
    
    # usage exmaple:
    class MyForm(forms.Form):
        point = SimplePointField()  # this one is required
        other_point = SimplePointField(required=False)  # can stay blank