From 0fda77e7772624091da1c56fca196e6def1b47ff Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Wed, 12 Sep 2018 10:47:43 +0000 Subject: [FIX] base_geolocalize allow to use Openstreetmap instead of Google - Change API provider to prevent invalid API calls - Move geo_find methods into base.geocoder object to ease inheritance - Add system parameter to allow use of another provider fixes #x26924 fixes #x6929 --- addons/base_geolocalize/models/base_geocoder.py | 136 ++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 addons/base_geolocalize/models/base_geocoder.py (limited to 'addons/base_geolocalize/models/base_geocoder.py') diff --git a/addons/base_geolocalize/models/base_geocoder.py b/addons/base_geolocalize/models/base_geocoder.py new file mode 100644 index 00000000000..87751cd400d --- /dev/null +++ b/addons/base_geolocalize/models/base_geocoder.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import json +import urllib2 +import logging + +from odoo import api, models, tools, _ +from odoo.exceptions import UserError + + +_logger = logging.getLogger(__name__) + + +class GeoCoder(models.AbstractModel): + """ + Abstract class used to call Geolocalization API and convert addresses + into GPS coordinates. + """ + _name = "base.geocoder" + + @api.model + def geo_query_address(self, street=None, zip=None, city=None, state=None, country=None): + """ Converts address fields into a valid string for querying + geolocation APIs. + :param street: street address + :param zip: zip code + :param city: city + :param state: state + :param country: country + :return: formatted string + """ + provider = self.env['ir.config_parameter'].get_param('base_geolocalize.provider', 'openstreetmap') + if hasattr(self, '_geo_query_address_' + provider): + # Makes the transformation defined for provider + service = getattr(self, '_geo_query_address_' + provider) + return service(street, zip, city, state, country) + else: + # By default, join the non-empty parameters + return tools.ustr(', '.join(filter(None, [ + street, + ("%s %s" % (zip or '', city or '')).strip(), + state, + country]))) + + @api.model + def geo_find(self, addr): + """Use a location provider API to convert an address string into a latitude, longitude tuple. + Here we use Openstreetmap Nominatim by default. + :param addr: Address string passed to API + :return: (latitude, longitude) or None if not found + """ + provider = self.env['ir.config_parameter'].get_param( + 'base_geolocalize.provider', 'openstreetmap') + try: + service = getattr(self, '_call_' + provider) + result = service(addr) + except AttributeError: + raise UserError(_( + 'Provider %s is not implemented for geolocation service.' + ) % provider) + except UserError: + raise + except: + _logger.debug('Geolocalize call failed', exc_info=True) + result = None + return result + + @api.model + def _call_openstreetmap(self, addr): + """ + Use Openstreemap Nominatim service to retrieve location + :return: (latitude, longitude) or None if not found + """ + if not addr: + _logger.info('invalid address given') + return None + url = 'https://nominatim.openstreetmap.org/search?format=json&q=' + url += urllib2.quote(addr.encode('utf8')) + try: + _logger.info('openstreetmap nominatim service called') + result = json.load(urllib2.urlopen(url)) + except Exception as e: + self._raise_internet_access_error(e) + geo = result[0] + return float(geo['lat']), float(geo['lon']) + + @api.model + def _call_google(self, addr): + """ Use google maps API. It won't work without a valid API key. + :return: (latitude, longitude) or None if not found + """ + apikey = self.env['ir.config_parameter'].sudo().get_param( + 'google.api_key_geocode') + if not apikey: + raise UserError(_( + "API key for GeoCoding (Places) required.\n" + "Save this key in System Parameters with key: google.api_key_geocode, value: Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information." + )) + url = 'https://maps.googleapis.com/maps/api/geocode/json?key=%s&sensor=false&address=' % apikey + url += urllib2.quote(addr.encode('utf8')) + try: + result = json.load(urllib2.urlopen(url)) + except Exception as e: + self._raise_internet_access_error(e) + + try: + if result['status'] != 'OK': + _logger.debug('Invalid Gmaps call: %s - %s', + result['status'], result.get('error_message', '')) + return None + geo = result['results'][0]['geometry']['location'] + return float(geo['lat']), float(geo['lng']) + except (KeyError, ValueError): + _logger.debug('Unexpected Gmaps API answer %s', result.get('error_message', '')) + return None + + @api.model + def _geo_query_address_google(self, street=None, zip=None, city=None, + state=None, country=None): + # This may be useful if using GMaps API. + # put country qualifier in front, otherwise GMap gives wrong + # results, e.g. 'Congo, Democratic Republic of the' => + # 'Democratic Republic of the Congo' + if country and ',' in country and ( + country.endswith(' of') or country.endswith(' of the')): + country = '{1} {0}'.format(*country.split(',', 1)) + return tools.ustr(', '.join(filter(None, [ + street, + ("%s %s" % (zip or '', city or '')).strip(), + state, + country]))) + + def _raise_internet_access_error(self, error): + raise UserError(_( + 'Cannot contact geolocation servers. Please make sure that your Internet connection is up and running (%s).' + ) % error) -- cgit v1.2.3 From 6c67539b92d52ae6c8449d7d2f63d032f805a903 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Thu, 15 Nov 2018 18:13:35 +0000 Subject: [REF] base_geolocalize, base_setup: ref + make it configurable in settings Migrate in v12 and Refactor a part of the code Allow to configure it in setting --- addons/base_geolocalize/models/base_geocoder.py | 93 ++++++++++++++----------- 1 file changed, 52 insertions(+), 41 deletions(-) (limited to 'addons/base_geolocalize/models/base_geocoder.py') diff --git a/addons/base_geolocalize/models/base_geocoder.py b/addons/base_geolocalize/models/base_geocoder.py index 87751cd400d..ee54fcf6dbd 100644 --- a/addons/base_geolocalize/models/base_geocoder.py +++ b/addons/base_geolocalize/models/base_geocoder.py @@ -1,22 +1,39 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import json -import urllib2 +import requests import logging -from odoo import api, models, tools, _ +from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) +class GeoProvider(models.Model): + _name = "base.geo_provider" + _description = "Geo Provider" + + tech_name = fields.Char() + name = fields.Char() + + class GeoCoder(models.AbstractModel): """ Abstract class used to call Geolocalization API and convert addresses into GPS coordinates. """ _name = "base.geocoder" + _description = "Geo Coder" + + @api.model + def _get_provider(self): + prov_id = self.env['ir.config_parameter'].sudo().get_param('base_geolocalize.geo_provider') + if prov_id: + provider = self.env['base.geo_provider'].browse(int(prov_id)) + if not prov_id or not provider.exists(): + provider = self.env['base.geo_provider'].search([], limit=1) + return provider @api.model def geo_query_address(self, street=None, zip=None, city=None, state=None, country=None): @@ -29,18 +46,13 @@ class GeoCoder(models.AbstractModel): :param country: country :return: formatted string """ - provider = self.env['ir.config_parameter'].get_param('base_geolocalize.provider', 'openstreetmap') + provider = self._get_provider().tech_name if hasattr(self, '_geo_query_address_' + provider): # Makes the transformation defined for provider - service = getattr(self, '_geo_query_address_' + provider) - return service(street, zip, city, state, country) + return getattr(self, '_geo_query_address_' + provider)(street, zip, city, state, country) else: # By default, join the non-empty parameters - return tools.ustr(', '.join(filter(None, [ - street, - ("%s %s" % (zip or '', city or '')).strip(), - state, - country]))) + return self._geo_query_address_default(street=street, zip=zip, city=city, state=state, country=country) @api.model def geo_find(self, addr): @@ -49,8 +61,7 @@ class GeoCoder(models.AbstractModel): :param addr: Address string passed to API :return: (latitude, longitude) or None if not found """ - provider = self.env['ir.config_parameter'].get_param( - 'base_geolocalize.provider', 'openstreetmap') + provider = self._get_provider().tech_name try: service = getattr(self, '_call_' + provider) result = service(addr) @@ -60,7 +71,7 @@ class GeoCoder(models.AbstractModel): ) % provider) except UserError: raise - except: + except Exception: _logger.debug('Geolocalize call failed', exc_info=True) result = None return result @@ -74,34 +85,31 @@ class GeoCoder(models.AbstractModel): if not addr: _logger.info('invalid address given') return None - url = 'https://nominatim.openstreetmap.org/search?format=json&q=' - url += urllib2.quote(addr.encode('utf8')) + url = 'https://nominatim.openstreetmap.org/search' try: + result = requests.get(url, {'format': 'json', 'q': addr}).json() _logger.info('openstreetmap nominatim service called') - result = json.load(urllib2.urlopen(url)) except Exception as e: - self._raise_internet_access_error(e) + self._raise_query_error(e) geo = result[0] return float(geo['lat']), float(geo['lon']) @api.model - def _call_google(self, addr): + def _call_googlemap(self, addr): """ Use google maps API. It won't work without a valid API key. :return: (latitude, longitude) or None if not found """ - apikey = self.env['ir.config_parameter'].sudo().get_param( - 'google.api_key_geocode') + apikey = self.env['ir.config_parameter'].sudo().get_param('base_geolocalize.google_map_api_key') if not apikey: raise UserError(_( "API key for GeoCoding (Places) required.\n" - "Save this key in System Parameters with key: google.api_key_geocode, value: Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information." + "Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information." )) - url = 'https://maps.googleapis.com/maps/api/geocode/json?key=%s&sensor=false&address=' % apikey - url += urllib2.quote(addr.encode('utf8')) + url = "https://maps.googleapis.com/maps/api/geocode/json" try: - result = json.load(urllib2.urlopen(url)) + result = requests.get(url, params={'sensor': 'false', 'address': addr, 'key': apikey}).json() except Exception as e: - self._raise_internet_access_error(e) + self._raise_query_error(e) try: if result['status'] != 'OK': @@ -115,22 +123,25 @@ class GeoCoder(models.AbstractModel): return None @api.model - def _geo_query_address_google(self, street=None, zip=None, city=None, - state=None, country=None): - # This may be useful if using GMaps API. - # put country qualifier in front, otherwise GMap gives wrong - # results, e.g. 'Congo, Democratic Republic of the' => - # 'Democratic Republic of the Congo' - if country and ',' in country and ( - country.endswith(' of') or country.endswith(' of the')): - country = '{1} {0}'.format(*country.split(',', 1)) - return tools.ustr(', '.join(filter(None, [ + def _geo_query_address_default(self, street=None, zip=None, city=None, state=None, country=None): + address_list = [ street, ("%s %s" % (zip or '', city or '')).strip(), state, - country]))) + country + ] + address_list = [item for item in address_list if item] + return tools.ustr(', '.join(address_list)) + + @api.model + def _geo_query_address_googlemap(self, street=None, zip=None, city=None, state=None, country=None): + # put country qualifier in front, otherwise GMap gives wrong# results + # e.g. 'Congo, Democratic Republic of the' => 'Democratic Republic of the Congo' + if country and ',' in country and ( + country.endswith(' of') or country.endswith(' of the')): + country = '{1} {0}'.format(*country.split(',', 1)) + return self._geo_query_address_default(street=street, zip=zip, city=city, state=state, country=country) + + def _raise_query_error(self, error): + raise UserError(_('Error with geolocation server:') + ' %s' % error) - def _raise_internet_access_error(self, error): - raise UserError(_( - 'Cannot contact geolocation servers. Please make sure that your Internet connection is up and running (%s).' - ) % error) -- cgit v1.2.3