11π
Hereβs my working custom field and widget:
class LatLongWidget(forms.MultiWidget):
"""
A Widget that splits Point input into two latitude/longitude boxes.
"""
def __init__(self, attrs=None, date_format=None, time_format=None):
widgets = (forms.TextInput(attrs=attrs),
forms.TextInput(attrs=attrs))
super(LatLongWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return tuple(reversed(value.coords))
return (None, None)
class LatLongField(forms.MultiValueField):
widget = LatLongWidget
srid = 4326
default_error_messages = {
'invalid_latitude' : _('Enter a valid latitude.'),
'invalid_longitude' : _('Enter a valid longitude.'),
}
def __init__(self, *args, **kwargs):
fields = (forms.FloatField(min_value=-90, max_value=90),
forms.FloatField(min_value=-180, max_value=180))
super(LatLongField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
if data_list:
# Raise a validation error if latitude or longitude is empty
# (possible if LatLongField has required=False).
if data_list[0] in validators.EMPTY_VALUES:
raise forms.ValidationError(self.error_messages['invalid_latitude'])
if data_list[1] in validators.EMPTY_VALUES:
raise forms.ValidationError(self.error_messages['invalid_longitude'])
# SRID=4326;POINT(1.12345789 1.123456789)
srid_str = 'SRID=%d'%self.srid
point_str = 'POINT(%f %f)'%tuple(reversed(data_list))
return ';'.join([srid_str, point_str])
return None
9π
The answers are great for building your own solution, but if you want a simple solution that looks good and is easy to use, try:
http://django-map-widgets.readthedocs.io/en/latest/index.html
You can:
- enter coordinates
- select directly on the map
- look up items from Google places.
- Django form with multiple file fields
- Setting Django admin display times to local time?
- Creating a Gin Index with Trigram (gin_trgm_ops) in Django model
8π
@Ramast, approaches the same issue in a more pythonic way. I have incorporated the changes to my code, it feels great to always improve.
This is how I managed -at last- to keep separate fields for lattitude and longitude without having to save them in the database since the values are already saved in the PointField.
The idea is :
- If we are inserting a new entry, the latitude and longitude fields will be used to set the PointField
- If we open an existing PointField entry, it will be used to provide the latitude and longitude values in the relevant FormFields.
models.py
from django.contrib.gis.db import models as geomodels
class Entry(geomodels.Model):
point = geomodels.PointField(
srid=4326,
blank=True,
)
admin.py
from myapp.forms import EntryForm
from django.contrib import admin
class EntryAdmin(admin.ModelAdmin):
form = EntryForm
admin.site.register(Entry, EntryAdmin)
forms.py
from django import forms
from myapp.models import Entry
from django.contrib.gis.geos import Point
class MarketEntryForm(forms.ModelForm):
latitude = forms.FloatField(
min_value=-90,
max_value=90,
required=True,
)
longitude = forms.FloatField(
min_value=-180,
max_value=180,
required=True,
)
class Meta(object):
model = MarketEntry
exclude = []
widgets = {'point': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
coordinates = self.initial.get('point', None)
if isinstance(coordinates, Point):
self.initial['longitude'], self.initial['latitude'] = coordinates.tuple
def clean(self):
data = super().clean()
latitude = data.get('latitude')
longitude = data.get('longitude')
point = data.get('point')
if latitude and longitude and not point:
data['point'] = Point(longitude, latitude)
return data
- Updating selection of objects, each with a different value in bulk (Django)
- Test if ValidationError was raised
4π
This is based on @rara_tiruβs solution but with some improvements
class GisForm(forms.ModelForm):
"""
This form can be used by any model that has "coordinates" field.
It will show a better looking map than the default one
"""
lat = forms.FloatField(required=False)
lng = forms.FloatField(required=False)
coordinates = PointField(widget=CustomPointWidget(), required=False, srid=4326)
def __init__(self, *args, **kwargs):
super(GisForm, self).__init__(*args, **kwargs)
coordinates = self.initial.get("coordinates", None)
if isinstance(coordinates, Point):
self.initial["lng"], self.initial["lat"] = coordinates.tuple
def clean(self):
data = super(GisForm, self).clean()
if "lat" in self.changed_data or "lng" in self.changed_data:
lat, lng = data.pop("lat", None), data.pop("lng", None)
data["coordinates"] = Point(lng, lat, srid=4326)
if not (data.get("coordinates") or data.get("lat")):
raise forms.ValidationError({"coordinates": "Coordinates is required"})
return data
This will basically allow users to either set location on map or manually put lat/lng values. once saved the map will reflect the specificed lat/lng value
- Django rest framework: Get detail view using a field other than primary key integer id
- Crispy-forms: add css class for one of the inputs
- Is it possible to generate PDF with StreamingHttpResponse as it's possible to do so with CSV for large dataset?
- Field Level Permission Django
- Django, REST and Angular Routes
- Django Javascript form validation
- Display query string values in django templates
- Adding a "through" table to django field and migrating with South?
0π
I myself was also looking for an answer to this. And being a beginner, I am quite confused by many different solutions. But I was able to achieve it with below code.
- Only Lat & Long text input is required to populate PointField and no map input is required and neither a map is seen in the form
- PointField can have null value
- Validates if the combination of the Lat Long value is set correct i.e., either both should be not null or both should be null.
Model Class
class DataSet(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=20, blank=False, null=False, unique=True)
latlong = geo_models.PointField(null=True,blank=True)
ModelForm Class
class DataSetForm(ModelForm):
lat = forms.FloatField(
min_value=-90,
max_value=90,
required=False,
)
lng = forms.FloatField(
min_value=-180,
max_value=180,
required=False,
)
class Meta:
model = DataSet
fields = ["name"]
labels = {"name": "Name", "lat": "Latitude", "lng": "Longitude"}
def clean(self):
super().clean()
data = self.cleaned_data
if (data['lat'] is None and data['lng'] is not None) or (
data['lat'] is not None and data['lng'] is None):
raise forms.ValidationError(
_('Both Lat Long Values must be set'), code='invalid')
View Class
class AddDataSet(CreateView):
model = DataSet
form_class = DataSetForm
template_name = "create.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({"title": "Create Dataset"})
return context
def form_valid(self, form):
lat = form.cleaned_data['lat']
lng = form.cleaned_data['lng']
response = super(CreateView, self).form_valid(form)
if (lat is not None and lng is not None):
self.object.latlong = Point(lat, lng)
self.object.save()
return response
I am not sure if there is a pitfall in this method. But it works as far as I tested.
- Django + mod_wsgi + apache: ImportError at / No module named djproj.urls
- Django Tutorial: name 'HttpResponse' is not defined
- Configuring django settings to work with 1.4.1. Loading template error
- Git push heroku master: Heroku push rejected, no Cedar-supported app detected
- Change row colour in Django Admin List