diff --git a/README.rst b/README.rst index fe31167..6ce2b92 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,9 @@ Installation pip install django-easy-maps Then add 'easy_maps' to INSTALLED_APPS and run ``./manage.py syncdb`` -(or ``./manage.py migrate easy_maps`` if South is in use) +(or ``./manage.py migrate easy_maps`` if South is in use). Since there are +some media files needed to be used, you have to collect the static files +distributed with this application (using ``./manage collectstatic``). Settings ======== @@ -27,6 +29,13 @@ then create a EASY_MAPS_GOOGLE_KEY in your settings.py file:: EASY_MAPS_GOOGLE_KEY = "your-google-maps-api-key" +If you need a place where center the map when no address is inserted yet add the +latitudine and longitude to the EASY_MAPS_CENTER_* variables in your settings.py +like the following:: + + EASY_MAPS_CENTER_LAT = -41.3 + EASY_MAPS_CENTER_LON = 15.2 + Usage ===== diff --git a/easy_maps/admin.py b/easy_maps/admin.py index 57a2b8d..c75c2d9 100644 --- a/easy_maps/admin.py +++ b/easy_maps/admin.py @@ -1,16 +1,59 @@ from django.contrib import admin from django import forms +from django.conf.urls.defaults import patterns, url +from django.http import HttpResponse, HttpResponseBadRequest +from django.views.decorators.csrf import csrf_exempt from .models import Address from .widgets import AddressWithMapWidget +from .geo import geolocalize + +import simplejson + +class AddressAdminForm(forms.ModelForm): + class Meta: + widgets = { + 'address': AddressWithMapWidget({'class': 'vTextField'}) + } class AddressAdmin(admin.ModelAdmin): - list_display = ['address', 'computed_address', 'latitude', 'longitude', 'geocode_error'] - list_filter = ['geocode_error'] search_fields = ['address'] + form = AddressAdminForm + class Media: + js = ( + 'https://maps.google.com/maps/api/js?sensor=false', + 'js/easy_maps.js', + ) + + def get_urls(self): + """Add a view that serves geolocalized data on POST request + """ + urls = super(AddressAdmin, self).get_urls() + my_urls = patterns('', + url(r'^geo/$', self.admin_site.admin_view(self.get_geo), name='address_json'), + ) + return my_urls + urls - class form(forms.ModelForm): - class Meta: - widgets = { - 'address': AddressWithMapWidget({'class': 'vTextField'}) + # FIXME: add CSRF protection look at https://docs.djangoproject.com/en/1.4/ref/contrib/csrf/#ajax + # for example in passing a CSRF token + @csrf_exempt + def get_geo(self, request): + """Return a json that will be used to insert correct value + into the model form. + """ + if request.method != "POST" or not request.POST.has_key('address') or request.POST['address'] == '': + return HttpResponseBadRequest() + + computed_address, latitude, longitude, geocode_error = geolocalize(request.POST["address"]) + return HttpResponse(simplejson.dumps( + { + 'computed_address': computed_address, + 'latitude': latitude, + 'longitude': longitude, + 'geocode_error': geocode_error, } + ), content_type='application/json') + +class AddressInlineAdmin(admin.StackedInline): + extra = 1 + form = AddressAdminForm diff --git a/easy_maps/geo.py b/easy_maps/geo.py new file mode 100644 index 0000000..7c5c7a9 --- /dev/null +++ b/easy_maps/geo.py @@ -0,0 +1,20 @@ +from django.conf import settings +from django.utils.encoding import smart_str + +from geopy import geocoders + +def geolocalize(address): + """From an address return the values needed to fullify an Address model form + """ + try: + if hasattr(settings, "EASY_MAPS_GOOGLE_KEY") and settings.EASY_MAPS_GOOGLE_KEY: + g = geocoders.Google(settings.EASY_MAPS_GOOGLE_KEY) + else: + g = geocoders.Google(resource='maps') + s_address = smart_str(address) + computed_address, (latitude, longitude,) = g.geocode(s_address, exactly_one=False)[0] + geocode_error = False + except (UnboundLocalError, ValueError,geocoders.google.GQueryError): + geocode_error = True + + return computed_address, latitude, longitude, geocode_error diff --git a/easy_maps/models.py b/easy_maps/models.py index 40ba9fd..92e7288 100644 --- a/easy_maps/models.py +++ b/easy_maps/models.py @@ -1,29 +1,22 @@ -from django.conf import settings from django.db import models -from django.utils.encoding import smart_str -from geopy import geocoders + +from .geo import geolocalize +from . import settings + class Address(models.Model): address = models.CharField(max_length=255, db_index=True) computed_address = models.CharField(max_length=255, null=True, blank=True) - latitude = models.FloatField(null=True, blank=True) - longitude = models.FloatField(null=True, blank=True) + latitude = models.FloatField(default=settings.EASY_MAPS_CENTER_LAT, null=True, blank=True) + longitude = models.FloatField(default=settings.EASY_MAPS_CENTER_LON, null=True, blank=True) geocode_error = models.BooleanField(default=False) def fill_geocode_data(self): if not self.address: self.geocode_error = True return - try: - if hasattr(settings, "EASY_MAPS_GOOGLE_KEY") and settings.EASY_MAPS_GOOGLE_KEY: - g = geocoders.Google(settings.EASY_MAPS_GOOGLE_KEY) - else: - g = geocoders.Google(resource='maps') - address = smart_str(self.address) - self.computed_address, (self.latitude, self.longitude,) = g.geocode(address, exactly_one=False)[0] - self.geocode_error = False - except (UnboundLocalError, ValueError,geocoders.google.GQueryError): - self.geocode_error = True + + self.computed_address, self.latitude, self.longitude, self.geocode_error = geolocalize(self.address) def save(self, *args, **kwargs): # fill geocode data if it is unknown @@ -38,3 +31,15 @@ class Meta: verbose_name = "EasyMaps Address" verbose_name_plural = "Address Geocoding Cache" + def json(self): + """Returns a JSON representation of the address data to be used + with the javascript in a template. + """ + import simplejson + dic = { + 'address': self.address, + 'computed_address': self.computed_address, + 'latitude': self.latitude, + 'longitude': self.longitude, + } + return simplejson.dumps(dic) diff --git a/easy_maps/settings.py b/easy_maps/settings.py new file mode 100644 index 0000000..a4b202c --- /dev/null +++ b/easy_maps/settings.py @@ -0,0 +1,4 @@ +from django.conf import settings + +EASY_MAPS_CENTER_LAT = getattr(settings, 'EASY_MAPS_CENTER_LAT', -34.397) +EASY_MAPS_CENTER_LON = getattr(settings, 'EASY_MAPS_CENTER_LON', 150.644) diff --git a/easy_maps/static/js/easy_maps.js b/easy_maps/static/js/easy_maps.js new file mode 100644 index 0000000..8d17cd3 --- /dev/null +++ b/easy_maps/static/js/easy_maps.js @@ -0,0 +1,59 @@ +// will contain the boundary of the map. +var g_lat_long_bound = new google.maps.LatLngBounds(); + +function easy_maps_set_form_value(id_prefix) { + return function (computed_address, lat, lng, error) { + document.getElementById(id_prefix + 'computed_address').value = computed_address; + document.getElementById(id_prefix + 'latitude').value = lat + document.getElementById(id_prefix + 'longitude').value = lng; + document.getElementById(id_prefix + 'geocode_error').value = error; + }; +} + +function easy_maps_bind_button (id_prefix) { + django.jQuery.post( + // FIXME: this is hardcoded + '/admin/easy_maps/address/geo/', { + //'{% url admin:address_json %}', { + 'address': document.getElementById(id_prefix + 'address').value + }, + function(data) { + easy_maps_set_form_value(id_prefix)( + data["computed_address"], + data["latitude"], + data["longitude"], + data["geocode_error"] + ); + var center = new google.maps.LatLng(data["latitude"], data["longitude"]); + marker.setPosition(center); + map.setCenter(center); + } + ); + + return false; +} + +function easy_maps_add_listener(id_prefix, marker) { + // update the coordinate on marker dragging + google.maps.event.addListener(marker, 'dragend', function(evt) { + var ll = marker.getPosition(); + // FIXME: fix id names + document.getElementById(id_prefix + 'latitude').value = ll.lat(); + document.getElementById(id_prefix + 'longitude').value = ll.lng(); + }); +} + +function easy_maps_add_marker(map, marker) { + var latlng = new google.maps.LatLng(marker.latitude, marker.longitude); + var marker = new google.maps.Marker({ + position: latlng, + map: map, + draggable: true, + title: marker.address + }); + + // add marker's coordinate to the boundary + g_lat_long_bound.extend(latlng); + + return marker; +} diff --git a/easy_maps/templates/admin/easy_maps/change_list.html b/easy_maps/templates/admin/easy_maps/change_list.html new file mode 100644 index 0000000..a3f11a1 --- /dev/null +++ b/easy_maps/templates/admin/easy_maps/change_list.html @@ -0,0 +1,6 @@ +{% extends "admin/change_list.html" %} +{% load easy_maps_tags %} +{% block result_list %} + {{block.super}} + {% easy_map cl.query_set 900 700 %} +{% endblock %} diff --git a/easy_maps/templates/easy_maps/map.html b/easy_maps/templates/easy_maps/map.html index 9f92899..7540297 100644 --- a/easy_maps/templates/easy_maps/map.html +++ b/easy_maps/templates/easy_maps/map.html @@ -1,21 +1,15 @@ -{% with map.latitude|stringformat:"f" as lat %} -{% with map.longitude|stringformat:"f" as long %} - -{% block api_js %} - - -{% endblock %} +{% load easy_maps_tags %} +{% with latitude|stringformat:"f" as lat %} +{% with longitude|stringformat:"f" as long %} {% block html %} -