Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request for comments #2

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.rst
Expand Up @@ -17,7 +17,9 @@ Installation
pip install django-easy-maps pip install django-easy-maps


Then add 'easy_maps' to INSTALLED_APPS and run ``./manage.py syncdb`` 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 Settings
======== ========
Expand All @@ -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" 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 Usage
===== =====


Expand Down
55 changes: 49 additions & 6 deletions easy_maps/admin.py
@@ -1,16 +1,59 @@
from django.contrib import admin from django.contrib import admin
from django import forms 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 .models import Address
from .widgets import AddressWithMapWidget 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): class AddressAdmin(admin.ModelAdmin):
list_display = ['address', 'computed_address', 'latitude', 'longitude', 'geocode_error']
list_filter = ['geocode_error']
search_fields = ['address'] 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): # FIXME: add CSRF protection look at https://docs.djangoproject.com/en/1.4/ref/contrib/csrf/#ajax
class Meta: # for example in passing a CSRF token
widgets = { @csrf_exempt
'address': AddressWithMapWidget({'class': 'vTextField'}) 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
20 changes: 20 additions & 0 deletions 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
35 changes: 20 additions & 15 deletions easy_maps/models.py
@@ -1,29 +1,22 @@
from django.conf import settings
from django.db import models 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): class Address(models.Model):
address = models.CharField(max_length=255, db_index=True) address = models.CharField(max_length=255, db_index=True)
computed_address = models.CharField(max_length=255, null=True, blank=True) computed_address = models.CharField(max_length=255, null=True, blank=True)
latitude = models.FloatField(null=True, blank=True) latitude = models.FloatField(default=settings.EASY_MAPS_CENTER_LAT, null=True, blank=True)
longitude = models.FloatField(null=True, blank=True) longitude = models.FloatField(default=settings.EASY_MAPS_CENTER_LON, null=True, blank=True)
geocode_error = models.BooleanField(default=False) geocode_error = models.BooleanField(default=False)


def fill_geocode_data(self): def fill_geocode_data(self):
if not self.address: if not self.address:
self.geocode_error = True self.geocode_error = True
return return
try:
if hasattr(settings, "EASY_MAPS_GOOGLE_KEY") and settings.EASY_MAPS_GOOGLE_KEY: self.computed_address, self.latitude, self.longitude, self.geocode_error = geolocalize(self.address)
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


def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# fill geocode data if it is unknown # fill geocode data if it is unknown
Expand All @@ -38,3 +31,15 @@ class Meta:
verbose_name = "EasyMaps Address" verbose_name = "EasyMaps Address"
verbose_name_plural = "Address Geocoding Cache" 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)
4 changes: 4 additions & 0 deletions 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)
59 changes: 59 additions & 0 deletions 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;
}
6 changes: 6 additions & 0 deletions 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 %}
47 changes: 25 additions & 22 deletions easy_maps/templates/easy_maps/map.html
@@ -1,21 +1,15 @@
{% with map.latitude|stringformat:"f" as lat %} {% load easy_maps_tags %}
{% with map.longitude|stringformat:"f" as long %} {% with latitude|stringformat:"f" as lat %}

{% with longitude|stringformat:"f" as long %}
{% block api_js %}
<!-- Google Maps API javascript -->
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
{% endblock %}


{% block html %} {% block html %}
<!-- HTML map container --> <!-- HTML map container -->
<div id="map-canvas-{{ map.pk }}" <div id="map-canvas-{{ id }}"
{% if width and map.latitude and not map.geocode_error %} style="width: {{ width }}px; height: {{ height }}px;"
style="width: {{ width }}px; height: {{ height }}px;"
{% endif %}
class="easy-map-googlemap"> class="easy-map-googlemap">
{% block noscript %} {% block noscript %}
<noscript> <noscript>
<img alt="Map of {{ map.address }}" src="https://maps.google.com/maps/api/staticmap?center={{ lat }},{{ long }}&zoom={{ zoom }}&markers={{ lat }},{{ long }}&size={{ width }}x{{ height }}&sensor=false"> <img alt="Map of {{ address }}" src="https://maps.google.com/maps/api/staticmap?center={{ lat }},{{ long }}&zoom={{ zoom }}&markers={{ lat }},{{ long }}&size={{ width }}x{{ height }}&sensor=false">
</noscript> </noscript>
{% endblock noscript %} {% endblock noscript %}
</div> </div>
Expand All @@ -24,10 +18,9 @@
{% block map_js %} {% block map_js %}
<!-- Map creation script --> <!-- Map creation script -->
<script type="text/javascript"> <script type="text/javascript">
function initialize_map_{{ map.pk }}() { function initialize_map_{{ id_safe }}() {
var latlng = new google.maps.LatLng({{ lat }}, {{ long }}); var latlng = new google.maps.LatLng({{ lat }}, {{ long }});
var mapElem = document.getElementById("map-canvas-{{ map.pk }}"); var mapElem = document.getElementById("map-canvas-{{ id }}");

{% block map_options_js %} {% block map_options_js %}
var mapOptions = { var mapOptions = {
zoom: {{ zoom }}, zoom: {{ zoom }},
Expand All @@ -39,20 +32,30 @@
var map = new google.maps.Map(mapElem, mapOptions); var map = new google.maps.Map(mapElem, mapOptions);


{% block extra_js %} {% block extra_js %}
var marker = new google.maps.Marker({ {% if markers %}
position: latlng, {% for marker in markers %}
map: map, var marker = easy_maps_add_marker(map, {{marker.json|safe}});
title: "{{ map.address }}" {% endfor %}
});
{% comment %}use the zoom level passed as argument if there is only one marker{% endcomment %}
{% if markers|length > 1 %}
// display all the markers
// http://blog.shamess.info/2009/09/29/zoom-to-fit-all-markers-on-google-maps-api-v3/
map.fitBounds(g_lat_long_bound);
{% else %}
easy_maps_add_listener("{{ id }}", marker);
{% endif %}
{% endif %}

{% endblock %} {% endblock %}
} }


{% block map_loading_js %} {% block map_loading_js %}
// initialize the map after page loading // initialize the map after page loading
google.maps.event.addDomListener(window, 'load', initialize_map_{{ map.pk }}); google.maps.event.addDomListener(window, 'load', initialize_map_{{ id_safe }});
{% endblock %} {% endblock %}
</script> </script>
{% endblock %} {% endblock %}


{% endwith %} {% endwith %}
{% endwith %} {% endwith %}
30 changes: 25 additions & 5 deletions easy_maps/templatetags/easy_maps_tags.py
@@ -1,14 +1,24 @@
#coding: utf-8 #coding: utf-8
from django import template from django import template
from django.template.loader import render_to_string from django.template.loader import render_to_string
from easy_maps.models import Address from ..models import Address
from .. import settings

register = template.Library() register = template.Library()


@register.tag @register.tag
def easy_map(parser, token): def easy_map(parser, token):
""" """
The syntax: The syntax:

{% easy_map <address> [<width> <height>] [<zoom>] [using <template_name>] %} {% easy_map <address> [<width> <height>] [<zoom>] [using <template_name>] %}

or

{% easy_map <addresses> [<width> <height>] [<zoom>] [using <template_name>] %}

where in the second case you pass a queryset containing the addresses to be
visualized.
""" """
width, height, zoom, template_name = None, None, None, None width, height, zoom, template_name = None, None, None, None
params = token.split_contents() params = token.split_contents()
Expand Down Expand Up @@ -42,16 +52,26 @@ def __init__(self, address, width, height, zoom, template_name):


def render(self, context): def render(self, context):
try: try:
address = self.address.resolve(context) address = self.address.resolve(context) or ''
template_name = self.template_name.resolve(context) template_name = self.template_name.resolve(context)


map, _ = Address.objects.get_or_create(address=address or '') if isinstance(address, basestring):
# if not exists the searched address then created an unsaved instance
try:
address = Address.objects.get(address=address)
except:
address = Address(address=address)

address = [address,]

context.update({ context.update({
'map': map, 'markers': address,
'width': self.width, 'width': self.width,
'height': self.height, 'height': self.height,
# FIXME: if the list is empty?
'latitude': address[0].latitude,
'longitude': address[0].longitude,
'zoom': self.zoom, 'zoom': self.zoom,
'template_name': template_name
}) })
return render_to_string(template_name, context_instance=context) return render_to_string(template_name, context_instance=context)
except template.VariableDoesNotExist: except template.VariableDoesNotExist:
Expand Down