[FIX][IMP] Make partner maps work properly

[FIX] website*: unfuck buggy controllers
[IMP] website*: display GoogleMap in a human-usable interface
[IMP] website_google_map: large module cleaning
      - There is now only one controller, data is sent once for all!
      - Map is now fully resizable in its hosting template
      - HTML/CSS cleaning
      - JavaScript is now human-readable ;-)
This commit is contained in:
Richard Mathot 2014-06-03 16:34:30 +02:00
parent c1ae43c807
commit f04e6de2e7
14 changed files with 231 additions and 194 deletions

View File

@ -146,7 +146,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
# Do not use semantic controller due to SUPERUSER_ID # Do not use semantic controller due to SUPERUSER_ID
@http.route(['/partners/<partner_id>'], type='http', auth="public", website=True) @http.route(['/partners/<partner_id>'], type='http', auth="public", website=True)
def partners_detail(self, partner_id, partner_name='', **post): def partners_detail(self, partner_id, partner_name='', **post):
mo = re.search('-([-0-9]+)$', str(partner_id)) mo = re.search('([-0-9]+)$', str(partner_id))
current_grade, current_country = None, None current_grade, current_country = None, None
grade_id = post.get('grade_id') grade_id = post.get('grade_id')
country_id = post.get('country_id') country_id = post.get('country_id')

View File

@ -115,10 +115,24 @@
<template id="ref_country" inherit_id="website_crm_partner_assign.index" optional="enabled" name="Left World Map"> <template id="ref_country" inherit_id="website_crm_partner_assign.index" optional="enabled" name="Left World Map">
<xpath expr="//ul[@id='reseller_countries']" position="after"> <xpath expr="//ul[@id='reseller_countries']" position="after">
<h3>World Map</h3> <!-- modal for large map -->
<div class="modal fade partner_map_modal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">World Map</h4>
</div>
<iframe t-attf-src="/google_map/?width=898&amp;height=485&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/partners/"
style="width:898px; height:485px; border:0; padding:0; margin:0;"></iframe>
</div>
</div>
</div>
<!-- modal end -->
<h3>World Map<button class="btn btn-link" data-toggle="modal" data-target=".partner_map_modal"><span class="fa fa-external-link" /></button></h3>
<ul class="nav"> <ul class="nav">
<iframe t-attf-src="/google_map/?width=260&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/partners" <iframe t-attf-src="/google_map/?width=260&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/partners/"
style="width:260px; height:260px; border:0; padding:0; margin:0;"></iframe> style="width:260px; height:240px; border:0; padding:0; margin:0;"></iframe>
</ul> </ul>
</xpath> </xpath>
</template> </template>

View File

@ -84,7 +84,7 @@ class WebsiteCustomer(http.Controller):
# Do not use semantic controller due to SUPERUSER_ID # Do not use semantic controller due to SUPERUSER_ID
@http.route(['/customers/<partner_id>'], type='http', auth="public", website=True) @http.route(['/customers/<partner_id>'], type='http', auth="public", website=True)
def partners_detail(self, partner_id, **post): def partners_detail(self, partner_id, **post):
mo = re.search('-([-0-9]+)$', str(partner_id)) mo = re.search('([-0-9]+)$', str(partner_id))
if mo: if mo:
partner_id = int(mo.group(1)) partner_id = int(mo.group(1))
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context) partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)

View File

@ -66,11 +66,27 @@
</template> </template>
<!-- Option: left column: World Map --> <!-- Option: left column: World Map -->
<template id="opt_country" inherit_id="website_customer.index" optional="disabled" name="Show Map"> <template id="opt_country" inherit_id="website_customer.index" optional="enabled" name="Show Map">
<xpath expr="//div[@id='ref_left_column']" position="inside"> <xpath expr="//div[@id='ref_left_column']" position="inside">
<!-- modal for large map -->
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/&amp;output=embed" <div class="modal fade customer_map_modal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
style="width:100%; border:0; padding:0; margin:0;"></iframe> <div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">World Map</h4>
</div>
<iframe t-attf-src="/google_map/?width=898&amp;height=485&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/"
style="width:898px; height:485px; border:0; padding:0; margin:0;"></iframe>
</div>
</div>
</div>
<!-- modal end -->
<h3>World Map<button class="btn btn-link" data-toggle="modal" data-target=".customer_map_modal"><span class="fa fa-external-link" /></button></h3>
<ul class="nav">
<iframe t-attf-src="/google_map/?width=260&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/"
style="width:260px; height:240px; border:0; padding:0; margin:0;"></iframe>
</ul>
</xpath> </xpath>
</template> </template>

View File

@ -1,2 +1 @@
import controllers import controllers
import models

View File

@ -1,3 +1,2 @@
import main import main
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,48 +1,64 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import openerp import json
from openerp import SUPERUSER_ID
from openerp.addons.web import http from openerp.addons.web import http
from openerp.addons.web.http import request from openerp.addons.web.http import request
from datetime import datetime
class google_map(http.Controller): class google_map(http.Controller):
'''
This class generates on-the-fly partner maps that can be reused in every
website page. To do so, just use an ``<iframe ...>`` whose ``src``
attribute points to ``/google_map`` (this controller generates a complete
HTML5 page).
URL query parameters:
- ``partner_ids``: a comma-separated list of ids (partners to be shown)
- ``partner_url``: the base-url to display the partner
(eg: if ``partner_url`` is ``/partners/``, when the user will click on
a partner on the map, it will be redirected to <myodoo>.com/partners/<id>)
In order to resize the map, simply resize the ``iframe`` with CSS
directives ``width`` and ``height``.
'''
@http.route(['/google_map'], type='http', auth="public", website=True) @http.route(['/google_map'], type='http', auth="public", website=True)
def google_map(self, *arg, **post): def google_map(self, *arg, **post):
cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry['res.partner']
# filter real ints from query parameters and build a domain
clean_ids = []
for s in post.get('partner_ids', "").split(","):
try:
i = int(s)
clean_ids.append(i)
except ValueError:
pass
# search for partners that can be displayed on a map
domain = [("id", "in", clean_ids), ('website_published', '=', True), ('is_company', '=', True)]
partners_ids = partner_obj.search(cr, SUPERUSER_ID, domain, context=context)
# browse and format data
partner_data = {
"counter": len(partners_ids),
"partners": []
}
request.context.update({'show_address': True})
for partner in partner_obj.browse(cr, SUPERUSER_ID, partners_ids, context=context):
partner_data["partners"].append({
'id': partner.id,
'name': partner.name,
'address': '\n'.join(partner.name_get()[0][1].split('\n')[1:]),
'latitude': partner.partner_latitude,
'longitude': partner.partner_longitude,
})
# generate the map
values = { values = {
'partner_ids': post.get('partner_ids', ""),
'width': post.get('width', 900),
'height': post.get('height', 460),
'partner_url': post.get('partner_url'), 'partner_url': post.get('partner_url'),
'partner_data': json.dumps(partner_data)
} }
return request.website.render("website_google_map.google_map", values) return request.website.render("website_google_map.google_map", values)
@http.route(['/google_map/partners.json'], type='http', auth="public", website=True)
def google_map_data(self, *arg, **post):
partner_obj = request.registry['res.partner']
domain = [("id", "in", [int(p) for p in post.get('partner_ids', "").split(",") if p])]
domain_public = domain + [('website_published', '=', True)]
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID,
domain_public, context=request.context)
return partner_obj.google_map_json(request.cr, openerp.SUPERUSER_ID,
partner_ids, request.context)
@http.route(['/google_map/set_partner_position'], type='http', methods=['POST'], auth="public", website=True)
def google_map_set_partner_position(self, *arg, **post):
partner_obj = request.registry['res.partner']
partner_id = post.get('partner_id') and int(post['partner_id'])
latitude = post.get('latitude') and float(post['latitude'])
longitude = post.get('longitude') and float(post['longitude'])
values = {
'partner_latitude': latitude,
'partner_longitude': longitude,
'date_localization': datetime.now().strftime('%Y-%m-%d'),
}
partner_obj.write(request.cr, openerp.SUPERUSER_ID, [partner_id], values,
request.context)
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1 +0,0 @@
import res_partner

View File

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv
import simplejson
import werkzeug.wrappers
class res_partner(osv.Model):
_inherit = 'res.partner'
def google_map_json(self, cr, uid, ids, context=None):
data = {
"counter": len(ids),
"partners": []
}
for partner in self.pool.get('res.partner').browse(cr, uid, ids, context={'show_address': True}):
data["partners"].append({
'id': partner.id,
'name': partner.name,
'address': '\n'.join(partner.name_get()[0][1].split('\n')[1:]),
'latitude': partner.partner_latitude,
'longitude': partner.partner_longitude,
})
mime = 'application/json'
body = "var data = " + "}, \n{".join(simplejson.dumps(data).split("}, {"))
return werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])

View File

@ -0,0 +1,28 @@
html {
height: 100%;
}
body {
margin: 0;
padding: 0;
height: 100%;
}
#odoo-google-map {
width: 100%;
height: 100%;
}
.marker {
font-size: 13px !important;
}
.marker a {
text-decoration: none;
}
.marker pre {
margin-top: 0;
margin-bottom: 0;
font-family: sans-serif !important;
}

View File

@ -1,84 +1,83 @@
function initialize(pt) { function initialize_map() {
var center = new google.maps.LatLng(10.91, 5.38); 'use strict';
var Geocoder = new google.maps.Geocoder();
var map = new google.maps.Map(document.getElementById('map'), { // MAP CONFIG AND LOADING
zoom: 1, var map = new google.maps.Map(document.getElementById('odoo-google-map'), {
center: center, zoom: 1,
mapTypeId: google.maps.MapTypeId.ROADMAP center: {lat: 0.0, lng: 0.0},
}); mapTypeId: google.maps.MapTypeId.ROADMAP
});
var infoWindow = new google.maps.InfoWindow(); // ENABLE ADRESS GEOCODING
var Geocoder = new google.maps.Geocoder();
google.maps.event.addListener(map, 'click', function() { // INFO BUBBLES
infoWindow.close(); var infoWindow = new google.maps.InfoWindow();
}); var partners = new google.maps.MarkerImage('/website_google_map/static/src/img/partners.png', new google.maps.Size(25, 25));
var partner_url = document.body.getAttribute('data-partner-url') || '';
var markers = [];
var partners = new google.maps.MarkerImage("/website_google_map/static/src/img/partners.png",new google.maps.Size(25, 25)); google.maps.event.addListener(map, 'click', function() {
infoWindow.close();
});
var markers = []; // Display the bubble once clicked
var onMarkerClick = function() {
var marker = this;
var p = marker.partner;
infoWindow.setContent(
'<div class="marker">'+
(partner_url.length ? '<a target="_top" href="'+partner_url+p.id+'"><b>'+p.name +'</b></a>' : '<b>'+p.name+'</b>' )+
(p.type ? ' <b>' + p.type + '</b>' : '')+
' <pre>' + p.address + '</pre>'+
'</div>'
);
infoWindow.open(map, marker);
};
var onMarkerClick = function() { // Create a bubble for a partner
var marker = this; var set_marker = function(partner) {
var p = marker.partner; // If no lat & long, geocode adress
infoWindow.setContent( // TODO: a server cronjob that will store these coordinates in database instead of resolving them on-the-fly
'<div class="marker">'+ if (!partner.latitude && !partner.longitude) {
(partner_url.length ? '<a target="_top" href="'+partner_url+p.id+'"><b>'+p.name +'</b></a>' : '<b>'+p.name+'</b>' )+ '<br/>'+ Geocoder.geocode({'address': partner.address}, function(results, status) {
(p.type ? ' <b>' + p.type + '</b>' : '')+ if (status === google.maps.GeocoderStatus.OK) {
' <pre>' + p.address + '</pre>'+ var location = results[0].geometry.location;
'</div>' partner.latitude = location.ob;
); partner.longitude = location.pb;
infoWindow.open(map, marker); var marker = new google.maps.Marker({
}; partner: partner,
map: map,
var set_marker = function(partner) { icon: partners,
if (!partner.latitude && !partner.longitude) { position: location
});
Geocoder.geocode( { 'address': partner.address}, function(results, status) { google.maps.event.addListener(marker, 'click', onMarkerClick);
if (status == google.maps.GeocoderStatus.OK) { markers.push(marker);
var location = results[0].geometry.location; } else {
console.debug('Geocode was not successful for the following reason: ' + status);
$.post("/google_map/set_partner_position", { }
'partner_id': partner.id, });
'latitude': location.ob,
'longitude': location.pb
});
partner.latitude = location.ob;
partner.longitude = location.pb;
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
partner: partner,
map: map,
icon: partners,
position: location
});
google.maps.event.addListener(marker, 'click', onMarkerClick);
markers.push(marker);
} else { } else {
console.debug('Geocode was not successful for the following reason: ' + status); var latLng = new google.maps.LatLng(partner.latitude, partner.longitude);
var marker = new google.maps.Marker({
partner: partner,
icon: partners,
map: map,
position: latLng
});
google.maps.event.addListener(marker, 'click', onMarkerClick);
markers.push(marker);
} }
}); };
} else { // Create the markers and cluster them on the map
if (odoo_partner_data){ /* odoo_partner_data special variable should have been defined in google_map.xml */
var latLng = new google.maps.LatLng(partner.latitude, partner.longitude); for (var i = 0; i < odoo_partner_data.counter; i++) {
var marker = new google.maps.Marker({ set_marker(odoo_partner_data.partners[i]);
partner: partner, }
icon: partners, var markerCluster = new MarkerClusterer(map, markers);
position: latLng
});
google.maps.event.addListener(marker, 'click', onMarkerClick);
markers.push(marker);
} }
};
if (data)
for (var i = 0; i < data.counter; i++) {
set_marker(data.partners[i]);
}
var markerCluster = new MarkerClusterer(map, markers);
} }
google.maps.event.addDomListener(window, 'load', initialize);
// Initialize map once the DOM has been loaded
google.maps.event.addDomListener(window, 'load', initialize_map);

View File

@ -1,42 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp> <openerp>
<data> <data>
<template id="google_map"> <template id="google_map">
&lt;!DOCTYPE html&gt; &lt;!DOCTYPE html&gt;
<html> <html>
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta charset="utf-8" />
<title>World wide map</title> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>World Map</title>
<style type="text/css"> <link rel="stylesheet" type="text/css" href="/website_google_map/static/src/css/google-map.css" />
body { </head>
margin: 0; <body t-att-data-partner-url="partner_url or ''">
padding: 0; <script>
font-family: Helvetica, Arial, sans-serif !important; var odoo_partner_data = <t t-raw="partner_data"/>;
} </script>
.marker { <div id="odoo-google-map"></div>
font-size: 12px !important; <script src="//maps.google.com/maps/api/js?sensor=false"></script>
} <script type="text/javascript" src="/website_google_map/static/src/js/markerclusterer_compiled.js"></script>
.marker b { <script type="text/javascript" src="/website_google_map/static/src/js/google_map.js"></script>
font-weight: 500; </body>
}
.marker pre {
font-size: 10px !important;
}
</style>
<script>var partner_url = '<t t-raw="partner_url or ''"/>';</script>
<script src="//maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" t-attf-src="/google_map/partners.json?partner_ids=#{ partner_ids }"></script>
<script type="text/javascript" src="/website_google_map/static/src/js/markerclusterer_compiled.js"></script>
<script src="//code.jquery.com/jquery-1.6.1.min.js"></script>
<script type="text/javascript" src="/website_google_map/static/src/js/google_map.js"></script>
</head>
<body>
<div id="map" t-attf-style="width: #{ width }px; height: #{ height }px"></div>
</body>
</html> </html>
</template>
</template> </data>
</data>
</openerp> </openerp>

View File

@ -105,7 +105,7 @@ class WebsiteMembership(http.Controller):
# Do not use semantic controller due to SUPERUSER_ID # Do not use semantic controller due to SUPERUSER_ID
@http.route(['/members/<partner_id>'], type='http', auth="public", website=True) @http.route(['/members/<partner_id>'], type='http', auth="public", website=True)
def partners_detail(self, partner_id, **post): def partners_detail(self, partner_id, **post):
mo = re.search('-([-0-9]+)$', str(partner_id)) mo = re.search('([-0-9]+)$', str(partner_id))
if mo: if mo:
partner_id = int(mo.group(1)) partner_id = int(mo.group(1))
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context) partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
@ -113,4 +113,4 @@ class WebsiteMembership(http.Controller):
values = {} values = {}
values['main_object'] = values['partner'] = partner values['main_object'] = values['partner'] = partner
return request.website.render("website_membership.partner", values) return request.website.render("website_membership.partner", values)
return self.customers(**post) return self.members(**post)

View File

@ -28,7 +28,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-4 mb32" id="left_column"> <div class="col-md-3 mb32" id="left_column">
<ul class="nav nav-pills nav-stacked mt16"> <ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>Associations</h3></li> <li class="nav-header"><h3>Associations</h3></li>
<li t-att-class="'' if membership else 'active'"><a href="/members">All</a></li> <li t-att-class="'' if membership else 'active'"><a href="/members">All</a></li>
@ -85,7 +85,7 @@
<template id="opt_index_country" name="Location" <template id="opt_index_country" name="Location"
optional="enabled" inherit_id="website_membership.index"> optional="enabled" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[last()]" position="after"> <xpath expr="//div[@id='left_column']/ul[1]" position="after">
<ul class="nav nav-pills nav-stacked mt16"> <ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>Location</h3></li> <li class="nav-header"><h3>Location</h3></li>
<t t-foreach="countries"> <t t-foreach="countries">
@ -102,13 +102,25 @@
<!-- Option: index: Left Google Map --> <!-- Option: index: Left Google Map -->
<template id="opt_index_google_map" name="Left World Map" <template id="opt_index_google_map" name="Left World Map"
optional="enabled" inherit_id="website_membership.index"> optional="enabled" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[1]" position="before"> <xpath expr="//div[@id='left_column']/ul[last()]" position="after">
<ul class="nav nav-pills nav-stacked mt16"> <!-- modal for large map -->
<li class="nav-header"><h3>World Map</h3></li> <div class="modal fade partner_map_modal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<ul class="nav"> <div class="modal-dialog modal-lg">
<iframe t-attf-src="/google_map/?width=320&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/members" <div class="modal-content">
style="width:320px; height:260px; border:0; padding:0; margin:0;"></iframe> <div class="modal-header">
</ul> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">World Map</h4>
</div>
<iframe t-attf-src="/google_map/?width=898&amp;height=485&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/members/"
style="width:898px; height:485px; border:0; padding:0; margin:0;"></iframe>
</div>
</div>
</div>
<!-- modal end -->
<h3>World Map<button class="btn btn-link" data-toggle="modal" data-target=".partner_map_modal"><span class="fa fa-external-link" /></button></h3>
<ul class="nav">
<iframe t-attf-src="/google_map/?width=260&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/members/"
style="width:260px; height:240px; border:0; padding:0; margin:0;"></iframe>
</ul> </ul>
</xpath> </xpath>
</template> </template>