[FIX][IMP] Make partner maps work properly
authorRichard Mathot <rim@openerp.com>
Tue, 3 Jun 2014 14:34:30 +0000 (16:34 +0200)
committerRichard Mathot <rim@openerp.com>
Tue, 3 Jun 2014 14:34:30 +0000 (16:34 +0200)
[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 ;-)

14 files changed:
addons/website_crm_partner_assign/controllers/main.py
addons/website_crm_partner_assign/views/website_crm_partner_assign.xml
addons/website_customer/controllers/main.py
addons/website_customer/views/website_customer.xml
addons/website_google_map/__init__.py
addons/website_google_map/controllers/__init__.py
addons/website_google_map/controllers/main.py
addons/website_google_map/models/__init__.py [deleted file]
addons/website_google_map/models/res_partner.py [deleted file]
addons/website_google_map/static/src/css/google-map.css [new file with mode: 0644]
addons/website_google_map/static/src/js/google_map.js
addons/website_google_map/views/google_map.xml
addons/website_membership/controllers/main.py
addons/website_membership/views/website_membership.xml

index c23bddc..7bec48c 100644 (file)
@@ -146,7 +146,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
     # Do not use semantic controller due to SUPERUSER_ID
     @http.route(['/partners/<partner_id>'], type='http', auth="public", website=True)
     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
         grade_id = post.get('grade_id')
         country_id = post.get('country_id')
index 20bfbd7..ad50213 100644 (file)
 
 <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">
-        <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">
-            <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>
+            <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:240px; border:0; padding:0; margin:0;"></iframe>
         </ul>
     </xpath>
 </template>
index 3e396f2..6310fe4 100644 (file)
@@ -84,7 +84,7 @@ class WebsiteCustomer(http.Controller):
     # Do not use semantic controller due to SUPERUSER_ID
     @http.route(['/customers/<partner_id>'], type='http', auth="public", website=True)
     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:
             partner_id = int(mo.group(1))
             partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
index f9bdea2..db64eb9 100644 (file)
 </template>
 
 <!-- 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">
-
-        <iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/&amp;output=embed"
-            style="width:100%; border:0; padding:0; margin:0;"></iframe>
+        <!-- modal for large map -->
+        <div class="modal fade customer_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=/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>
 </template>
 
index 65221e1..ee59594 100644 (file)
@@ -1,2 +1 @@
 import controllers
-import models
\ No newline at end of file
index 9de241a..efddba1 100644 (file)
@@ -1,48 +1,64 @@
 # -*- coding: utf-8 -*-
 
-import openerp
+import json
+from openerp import SUPERUSER_ID
 from openerp.addons.web import http
 from openerp.addons.web.http import request
-from datetime import datetime
+
 
 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)
     def google_map(self, *arg, **post):
-        values = {
-            'partner_ids': post.get('partner_ids', ""),
-            'width': post.get('width', 900),
-            'height': post.get('height', 460),
-            'partner_url': post.get('partner_url'),
-        }
-        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):
+        cr, uid, context = request.cr, request.uid, request.context
         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)
+        # 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
 
-    @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']
+        # 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)
 
-        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'])
+        # 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 = {
-            'partner_latitude': latitude,
-            'partner_longitude': longitude,
-            'date_localization': datetime.now().strftime('%Y-%m-%d'),
+            'partner_url': post.get('partner_url'),
+            'partner_data': json.dumps(partner_data)
         }
-        partner_obj.write(request.cr, openerp.SUPERUSER_ID, [partner_id], values,
-                          request.context)
-
-
-# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
+        return request.website.render("website_google_map.google_map", values)
diff --git a/addons/website_google_map/models/__init__.py b/addons/website_google_map/models/__init__.py
deleted file mode 100644 (file)
index f6dfb12..0000000
+++ /dev/null
@@ -1 +0,0 @@
-import res_partner
\ No newline at end of file
diff --git a/addons/website_google_map/models/res_partner.py b/addons/website_google_map/models/res_partner.py
deleted file mode 100644 (file)
index 26da7df..0000000
+++ /dev/null
@@ -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))])
diff --git a/addons/website_google_map/static/src/css/google-map.css b/addons/website_google_map/static/src/css/google-map.css
new file mode 100644 (file)
index 0000000..3d5ec1d
--- /dev/null
@@ -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;
+}
index 19f4a5e..53e5342 100644 (file)
@@ -1,84 +1,83 @@
-function initialize(pt) {
-  var center = new google.maps.LatLng(10.91, 5.38);
-  var Geocoder = new google.maps.Geocoder();
+function initialize_map() {
+    'use strict';
 
-  var map = new google.maps.Map(document.getElementById('map'), {
-    zoom: 1,
-    center: center,
-    mapTypeId: google.maps.MapTypeId.ROADMAP
-  });
+    // MAP CONFIG AND LOADING
+    var map = new google.maps.Map(document.getElementById('odoo-google-map'), {
+        zoom: 1,
+        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() {
-     infoWindow.close();
-  });
+    // INFO BUBBLES
+    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() {
-    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>' )+ '<br/>'+
-          (p.type ? '  <b>' + p.type + '</b>' : '')+
-          '  <pre>' + p.address + '</pre>'+
-          '</div>'
-      );
-    infoWindow.open(map, marker);
-  };
-
-  var set_marker = function(partner) {
-    if (!partner.latitude && !partner.longitude) {
-
-      Geocoder.geocode( { 'address': partner.address}, function(results, status) {
-        if (status == google.maps.GeocoderStatus.OK) {
-          var location = results[0].geometry.location;
-
-          $.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);
+    // Create a bubble for a partner
+    var set_marker = function(partner) {
+        // If no lat & long, geocode adress
+        // TODO: a server cronjob that will store these coordinates in database instead of resolving them on-the-fly
+        if (!partner.latitude && !partner.longitude) {
+            Geocoder.geocode({'address': partner.address}, function(results, status) {
+                if (status === google.maps.GeocoderStatus.OK) {
+                    var location = results[0].geometry.location;
+                    partner.latitude = location.ob;
+                    partner.longitude = location.pb;
+                    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 {
+                    console.debug('Geocode was not successful for the following reason: ' + status);
+                }
+            });
         } 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 {
-
-      var latLng = new google.maps.LatLng(partner.latitude, partner.longitude);
-      var marker = new google.maps.Marker({
-        partner: partner,
-        icon: partners,
-        position: latLng
-      });
-      google.maps.event.addListener(marker, 'click', onMarkerClick);
-      markers.push(marker);
+    // 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 */
+        for (var i = 0; i < odoo_partner_data.counter; i++) {
+            set_marker(odoo_partner_data.partners[i]);
+        }
+        var markerCluster = new MarkerClusterer(map, markers);
     }
-
-  };
-
-  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);
index d9a9d27..e68b9f9 100644 (file)
@@ -1,42 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
-    <data>
-        <template id="google_map">
-
+<data>
+<template id="google_map">
 &lt;!DOCTYPE html&gt;
 <html>
-  <head>
-    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
-    <title>World wide map</title>
-
-    <style type="text/css">
-      body {
-        margin: 0;
-        padding: 0;
-        font-family: Helvetica, Arial, sans-serif !important;
-      }
-      .marker {
-        font-size: 12px !important;
-      }
-      .marker b {
-        font-weight: 500;
-      }
-      .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>
+    <head>
+        <meta charset="utf-8" />
+        <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+        <title>World Map</title>
+        <link rel="stylesheet" type="text/css" href="/website_google_map/static/src/css/google-map.css" />
+    </head>
+    <body t-att-data-partner-url="partner_url or ''">
+        <script>
+            var odoo_partner_data = <t t-raw="partner_data"/>;
+        </script>
+        <div id="odoo-google-map"></div>
+        <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>
+        <script type="text/javascript" src="/website_google_map/static/src/js/google_map.js"></script>
+    </body>
 </html>
-
-        </template>
-     </data>
+</template>
+</data>
 </openerp>
index f4eb4bc..171b568 100644 (file)
@@ -105,7 +105,7 @@ class WebsiteMembership(http.Controller):
     # Do not use semantic controller due to SUPERUSER_ID
     @http.route(['/members/<partner_id>'], type='http', auth="public", website=True)
     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:
             partner_id = int(mo.group(1))
             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['main_object'] = values['partner'] = partner
                 return request.website.render("website_membership.partner", values)
-        return self.customers(**post)
+        return self.members(**post)
index 88c3ade..516b4b9 100644 (file)
@@ -28,7 +28,7 @@
             <div class="container">
                 <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">
                     <li class="nav-header"><h3>Associations</h3></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"
         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">
             <li class="nav-header"><h3>Location</h3></li>
             <t t-foreach="countries">
 <!-- Option: index: Left Google Map -->
 <template id="opt_index_google_map" name="Left World Map"
         optional="enabled" inherit_id="website_membership.index">
-    <xpath expr="//div[@id='left_column']/ul[1]" position="before">
-        <ul class="nav nav-pills nav-stacked mt16">
-            <li class="nav-header"><h3>World Map</h3></li>
-            <ul class="nav">
-                <iframe t-attf-src="/google_map/?width=320&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/members"
-                    style="width:320px; height:260px; border:0; padding:0; margin:0;"></iframe>
-            </ul>
+    <xpath expr="//div[@id='left_column']/ul[last()]" position="after">
+        <!-- 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=/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>
     </xpath>
 </template>