diff --git a/.travis.yml b/.travis.yml index f549270598..161f4128b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ install: - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - travis_install_nightly - - pip install geojson Shapely sphinx sphinx_bootstrap_theme sphinx-intl odoo-sphinx-autodoc + - pip install geojson Shapely sphinx sphinx_bootstrap_theme sphinx-intl odoo-sphinx-autodoc responses # This is to solve an issue with stdout file descriptor # raising a BlockingIOError while executing pylint and returns an exit -1 # seems related to the numerous lint errors on web_view_google_map module diff --git a/base_geoengine/fields.py b/base_geoengine/fields.py index 28c6ea793f..f8c1022f7e 100644 --- a/base_geoengine/fields.py +++ b/base_geoengine/fields.py @@ -12,6 +12,7 @@ logger = logging.getLogger(__name__) try: + from shapely.geometry import asShape from shapely.geometry import Point from shapely.geometry.base import BaseGeometry from shapely.wkb import loads as wkbloads @@ -20,6 +21,7 @@ logger.warning('Shapely or geojson are not available in the sys path') +# pylint: disable=sql-injection, invalid-commit class GeoField(Field): """ The field descriptor contains the field definition common to all specialized fields for geolocalization. Subclasses must define a type @@ -39,7 +41,7 @@ def column_type(self): _slots = { 'dim': 2, - 'srid': 900913, + 'srid': 3857, 'gist_index': True, 'manual': True, } @@ -54,8 +56,7 @@ def convert_to_column(self, value, record): shape_to_write = self.entry_to_shape(value, same_type=True) if shape_to_write.is_empty: return None - else: - return shape_to_write.wkt + return shape_to_write.wkt def convert_to_cache(self, value, record, validate=True): val = value @@ -64,15 +65,14 @@ def convert_to_cache(self, value, record, validate=True): return val def convert_to_record(self, value, record): + """ Value may be: + - a GeoJSON string when field onchange is triggered + - a geometry object hexcode from cache + - a unicode containing dict + """ if not value: return False - if isinstance(value, str): - # Geometry object hexcode from cache - shape = self.load_geo(value) - else: - # Might be unicode containing dict - shape = convert.value_to_shape(value) - return shape + return convert.value_to_shape(value, use_wkb=True) def convert_to_read(self, value, record, use_name_get=True): if not isinstance(value, BaseGeometry): @@ -96,6 +96,8 @@ def convert_to_read(self, value, record, use_name_get=True): @classmethod def load_geo(cls, wkb): """Load geometry into browse record after read was done""" + if isinstance(wkb, BaseGeometry): + return wkb return wkbloads(wkb, hex=True) if wkb else False def create_geo_column(self, cr, col_name, table, model): @@ -115,7 +117,6 @@ def create_geo_column(self, cr, col_name, table, model): raise finally: cr.commit() - return True def entry_to_shape(self, value, same_type=False): @@ -188,6 +189,7 @@ def update_geo_column(self, cr, col_name, table, model): class GeoLine(GeoField): """Field for POSTGIS geometry Line type""" + type = 'geo_line' geo_type = 'LINESTRING' @@ -255,6 +257,31 @@ def from_latlon(cls, cr, latitude, longitude): res = cr.fetchone() return cls.load_geo(res[0]) + @classmethod + def to_latlon(cls, cr, geopoint): + """ Convert a UTM coordinate point to (latitude, longitude): + """ + # Line to execute to retrieve longitude, latitude from UTM + # in postgres command line: + # SELECT ST_X(geom), ST_Y(geom) FROM (SELECT ST_TRANSFORM(ST_SetSRID( + # ST_MakePoint(601179.61612, 6399375,681364), 3847), 4326) as geom) g; + if isinstance(geopoint, BaseGeometry): + geo_point_instance = geopoint + else: + geo_point_instance = asShape(geojson.loads(geopoint)) + cr.execute(""" + SELECT + ST_TRANSFORM( + ST_SetSRID(ST_MakePoint(%(coord_x)s, %(coord_y)s), + %(srid)s), 4326)""", + {'coord_x': geo_point_instance.x, + 'coord_y': geo_point_instance.y, + 'srid': cls._slots['srid']}) + + res = cr.fetchone() + point_latlon = cls.load_geo(res[0]) + return point_latlon.x, point_latlon.y + class GeoPolygon(GeoField): """Field for POSTGIS geometry Polygon type""" diff --git a/base_geoengine/geo_db.py b/base_geoengine/geo_db.py index 30eab43d03..12b60b6141 100644 --- a/base_geoengine/geo_db.py +++ b/base_geoengine/geo_db.py @@ -4,6 +4,7 @@ """Helper to setup Postgis""" import logging +from odoo import _ from odoo.exceptions import MissingError logger = logging.getLogger('geoengine.sql') @@ -33,12 +34,14 @@ def init_postgis(cr): """) except Exception: raise MissingError( - "Error, can not automatically initialize spatial postgis support. " - "Database user may have to be superuser and postgres/postgis " - "extentions with their devel header have to be installed. " - "If you do not want Odoo to connect with a super user " - "you can manually prepare your database. To do this" - "open a client to your database using a super user and run: \n" - "CREATE EXTENSION postgis;\n" - "CREATE EXTENSION postgis_topology;\n" + _("Error," + "can not automatically initialize spatial postgis support." + "Database user may have to be superuser and postgres/postgis " + "extentions with their devel header have to be installed. " + "If you do not want Odoo to connect with a super user " + "you can manually prepare your database. To do this" + "open a client to your database using a super user and run: \n" + "CREATE EXTENSION postgis;\n" + "CREATE EXTENSION postgis_topology;\n") + ) diff --git a/base_geoengine/geo_helper/geo_convertion_helper.py b/base_geoengine/geo_helper/geo_convertion_helper.py index 8edad92d50..279b26b12f 100644 --- a/base_geoengine/geo_helper/geo_convertion_helper.py +++ b/base_geoengine/geo_helper/geo_convertion_helper.py @@ -4,7 +4,7 @@ import logging try: - from shapely import wkt + from shapely import wkt, wkb from shapely.geometry import asShape from shapely.geometry.base import BaseGeometry import geojson @@ -13,7 +13,7 @@ logger.warning('Shapely or geojson are not available in the sys path') -def value_to_shape(value): +def value_to_shape(value, use_wkb=False): """Transforms input into a Shapely object""" if not value: return wkt.loads('GEOMETRYCOLLECTION EMPTY') @@ -23,6 +23,8 @@ def value_to_shape(value): if '{' in value: geo_dict = geojson.loads(value) return asShape(geo_dict) + elif use_wkb: + return wkb.loads(value, hex=True) else: return wkt.loads(value) elif hasattr(value, 'wkt'): diff --git a/base_geoengine/geo_operators.py b/base_geoengine/geo_operators.py index 2d105f6b8b..b497bd2bb3 100644 --- a/base_geoengine/geo_operators.py +++ b/base_geoengine/geo_operators.py @@ -45,6 +45,7 @@ def geo_search(model, domain=None, geo_domain=None, offset=0, * geo_contains * geo_intersect """ + cr = model._cr domain = domain or [] geo_domain = geo_domain or [] @@ -114,9 +115,12 @@ def geo_search(model, domain=None, geo_domain=None, offset=0, where_statement = " WHERE %s" % (u' '.join(where_clause_arr)) else: where_statement = u'' + + # pylint: disable=sql-injection sql = 'SELECT "%s".id FROM ' % model._table + from_clause + \ where_statement + order_by + limit_str + offset_str # logger.debug(cursor.mogrify(sql, where_clause_params)) + cr.execute(sql, where_clause_params) res = cr.fetchall() if res: diff --git a/base_geoengine/tests/data.py b/base_geoengine/tests/data.py index 9c51325da9..c71ecbe634 100644 --- a/base_geoengine/tests/data.py +++ b/base_geoengine/tests/data.py @@ -118,7 +118,7 @@ "the_geom": { "geo_type": { "dim": 2, - "srid": 900913, + "srid": 3857, "type": "geo_multi_polygon" }, "required": False, @@ -138,5 +138,5 @@ 'default_extent': u'-123164.85222423, 5574694.9538936, 1578017.6490538, 6186191.1800898', 'geo_type': 'MULTIPOLYGON', - 'srid': 900913 + 'srid': 3857 } diff --git a/base_geoengine/tests/test_geoengine.py b/base_geoengine/tests/test_geoengine.py index 5831adf931..689c6ebc33 100644 --- a/base_geoengine/tests/test_geoengine.py +++ b/base_geoengine/tests/test_geoengine.py @@ -16,8 +16,9 @@ from odoo import fields from odoo.exceptions import MissingError -from odoo.addons.base_geoengine.geo_model import GeoModel -from odoo.addons.base_geoengine import fields as geo_fields +from ..geo_model import GeoModel +from .. import fields as geo_fields +from ..fields import GeoPoint from .data import MULTIPOLYGON_1, GEO_VIEW, FORM_VIEW from .data import EXPECTED_GEO_COLUMN_MULTIPOLYGON @@ -272,3 +273,25 @@ def test_create_line_from_points(self): geo_line = geo_fields.GeoLine.from_points( self.env.cr, geo_point_1, geo_point_2) self.assertEqual(geo_line, expected_line) + + def test_from_lat_lon(self): + latitude = 49.72842315886126 + longitude = 5.400488376617026 + + # This is computed with postgis in postgres: + + expected_coordinates = [601179.61612, 6399375.681364] + + geo_point = GeoPoint.from_latlon(self.env.cr, latitude, longitude) + + self.assertAlmostEqual(geo_point.x, expected_coordinates[0], 4) + self.assertAlmostEqual(geo_point.y, expected_coordinates[1], 4) + + def test_to_lat_lon(self): + + geo_point = Point(601179.61612, 6399375.681364) + + longitude, latitude = GeoPoint.to_latlon(self.env.cr, geo_point) + + self.assertAlmostEqual(latitude, 49.72842315886126, 4) + self.assertAlmostEqual(longitude, 5.400488376617026, 4) diff --git a/base_geoengine_demo/models/geo_npa.py b/base_geoengine_demo/models/geo_npa.py index 80b8831e5f..101fe7a5a8 100644 --- a/base_geoengine_demo/models/geo_npa.py +++ b/base_geoengine_demo/models/geo_npa.py @@ -18,12 +18,12 @@ class NPA(geo_model.GeoModel): city = fields.Char('City', size=64, index=True, required=True) the_geom = geo_fields.GeoMultiPolygon('NPA Shape') total_sales = fields.Float( - compute='_get_ZIP_total_sales', + compute='_compute_ZIP_total_sales', string='Spatial! Total Sales', ) @api.multi - def _get_ZIP_total_sales(self): + def _compute_ZIP_total_sales(self): """Return the total of the invoiced sales for this npa""" mach_obj = self.env['geoengine.demo.automatic.retailing.machine'] for rec in self: diff --git a/base_geoengine_demo/models/retail_machine.py b/base_geoengine_demo/models/retail_machine.py index a8ed74d6d8..60db67d214 100644 --- a/base_geoengine_demo/models/retail_machine.py +++ b/base_geoengine_demo/models/retail_machine.py @@ -7,8 +7,8 @@ from odoo.addons.base_geoengine import geo_model from odoo.addons.base_geoengine import fields as geo_fields - logger = logging.getLogger(__name__) + try: import geojson except ImportError: diff --git a/base_geolocalize_openstreetmap/README.rst b/base_geolocalize_openstreetmap/README.rst new file mode 100644 index 0000000000..3ed5e0d1cc --- /dev/null +++ b/base_geolocalize_openstreetmap/README.rst @@ -0,0 +1,55 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============================== +Base Geolocalize Openstreetmap +============================== + +Open street map API call to geolocalize an address. + +Installation +============ + +Just install it! + +Configuration +============= + +No configuration needed. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Benjamin Willig + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/base_geolocalize_openstreetmap/__init__.py b/base_geolocalize_openstreetmap/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/base_geolocalize_openstreetmap/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/base_geolocalize_openstreetmap/__manifest__.py b/base_geolocalize_openstreetmap/__manifest__.py new file mode 100644 index 0000000000..1d6de72ec5 --- /dev/null +++ b/base_geolocalize_openstreetmap/__manifest__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Base Geolocalize Openstreetmap', + 'summary': """ + Open street map API call to geolocalize an address""", + 'version': '10.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'ACSONE SA/NV, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/geospatial', + 'depends': [ + 'base_geolocalize', + ], + 'external_dependencies': { + 'python': [ + 'requests' + ], + }, +} diff --git a/base_geolocalize_openstreetmap/models/__init__.py b/base_geolocalize_openstreetmap/models/__init__.py new file mode 100644 index 0000000000..91fed54d40 --- /dev/null +++ b/base_geolocalize_openstreetmap/models/__init__.py @@ -0,0 +1 @@ +from . import res_partner diff --git a/base_geolocalize_openstreetmap/models/res_partner.py b/base_geolocalize_openstreetmap/models/res_partner.py new file mode 100644 index 0000000000..7bbe07b565 --- /dev/null +++ b/base_geolocalize_openstreetmap/models/res_partner.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import logging +import requests + +from odoo import exceptions, models, _ + +_logger = logging.getLogger(__name__) + + +class ResPartner(models.AbstractModel): + + _url = 'https://nominatim.openstreetmap.org/search' + _inherit = 'res.partner' + + @classmethod + def _geocode_address(cls, street=None, + zip_code=None, city=None, state=None, country=None): + """Get the latitude and longitude by requesting Openstreetmap" + """ + pay_load = { + 'limit': 1, + 'format': 'json', + 'street': street or '', + 'postalCode': zip_code or '', + 'city': city or '', + 'state': state or '', + 'country': country or '', + } + + request_result = requests.get(cls._url, params=pay_load) + try: + request_result.raise_for_status() + except Exception as e: + _logger.exception('Geocoding error') + raise exceptions.Warning( + _('Geocoding error. \n %s') % e.message) + values = request_result.json() + values = values[0] if values else {} + return values + + @classmethod + def _geo_localize(cls, + apikey, street='', zip='', + city='', state='', country=''): + # pylint: disable=W0622 + result = cls._geocode_address(street=street, + zip_code=zip, city=city, + state=state, country=country) + + if not result: + result = cls._geocode_address(city=city, + state=state, country=country) + + return result.get('lat'), result.get('lon') diff --git a/base_geolocalize_openstreetmap/static/description/icon.png b/base_geolocalize_openstreetmap/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/base_geolocalize_openstreetmap/static/description/icon.png differ diff --git a/base_geolocalize_openstreetmap/tests/__init__.py b/base_geolocalize_openstreetmap/tests/__init__.py new file mode 100644 index 0000000000..661db737e0 --- /dev/null +++ b/base_geolocalize_openstreetmap/tests/__init__.py @@ -0,0 +1 @@ +from . import test_geolocalize_openstreetmap diff --git a/base_geolocalize_openstreetmap/tests/test_geolocalize_openstreetmap.py b/base_geolocalize_openstreetmap/tests/test_geolocalize_openstreetmap.py new file mode 100644 index 0000000000..e57c0c9b62 --- /dev/null +++ b/base_geolocalize_openstreetmap/tests/test_geolocalize_openstreetmap.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import responses +import odoo.tests.common as common + + +class TestGeolocalizeOpenstreetmap(common.TransactionCase): + + def setUp(self): + super(TestGeolocalizeOpenstreetmap, self).setUp() + self.expected_latitude = 50.4311411 + self.expected_longitude = 4.6132813 + + # First, test a street known by OSM + vals = { + 'name': 'Partner Project', + 'street': 'Rue bois des noix', + 'country_id': self.env.ref('base.be').id, + 'zip': '5060', + 'city': 'Tamines', + 'state': 'Namur' + } + + self.partner_id_known = self.env['res.partner'].create(vals) + + # Second, test a fake street to force the code to go to + # the second iteration + vals2 = { + 'name': 'Partner Project', + 'street': 'Rue test', + 'city': 'Tamines', + 'country_id': self.env.ref('base.be').id, + } + self.partner_id_known_second = self.env['res.partner'].create(vals2) + + # Third, test a fake street to force the code to go + # to the second iteration + vals3 = { + 'name': 'Partner Project', + 'street': 'Rue test', + 'city': 'Tmnss', + 'country_id': self.env.ref('base.be').id, + } + self.partner_id_not_known = self.env['res.partner'].create(vals3) + + @responses.activate + def test_osm_found_lat_long_with_mock(self): + responses.add( + responses.Response( + method='GET', + url='https://nominatim.openstreetmap.org/search?' + + 'city=Tamines&format=json&country=Belgium&' + + 'state=&street=Rue+bois+des+noix&limit=1&postalCode=5060', + match_querystring=True, + json=[{'lat': self.expected_latitude, + 'lon': self.expected_longitude}], + status=200 + )) + + self.partner_id_known.geo_localize() + + self.assertEqual(len(responses.calls), 1, 'call does not exist') + self.assertAlmostEqual( + self.partner_id_known.partner_latitude, self.expected_latitude, 3, + 'Latitude Should be equals') + self.assertAlmostEqual( + self.partner_id_known.partner_longitude, + self.expected_longitude, 3, + 'Longitude Should be equals') + + @responses.activate + def test_osm_found_lat_long_second_time_with_mock(self): + responses.add( + responses.Response( + method='GET', + url='https://nominatim.openstreetmap.org/search?city=Tamines' + + '&format=json&country=Belgium&state=&street=Rue+test&' + + 'limit=1&postalCode=', + match_querystring=True, + json=[{}], + status=200 + )) + responses.add( + responses.Response( + method='GET', + url='https://nominatim.openstreetmap.org/search?city=Tamines' + + '&format=json&country=Belgium&state=&street=&' + + 'limit=1&postalCode=', + match_querystring=True, + json=[{'lat': 50.825833, 'lon': 4.3475227}], + status=200 + )) + + self.partner_id_known_second.geo_localize() + + self.assertAlmostEqual( + self.partner_id_known_second.partner_latitude, 50.825833, 3, + 'Latitude Should be equals') + self.assertAlmostEqual( + self.partner_id_known_second.partner_longitude, 4.3475227, 3, + 'Longitude Should be equals') + + @responses.activate + def test_osm_loc_not_found_with_mock(self): + + responses.add( + responses.Response( + method='GET', + url='https://nominatim.openstreetmap.org/search?city=Tmnss&' + + 'format=json&country=Belgium&state=&street=Rue+test&' + + 'limit=1&postalCode=', + match_querystring=True, + json=[{}] + )) + + responses.add( + responses.Response( + method='GET', + url='https://nominatim.openstreetmap.org/search?city=Tmnss&' + + 'format=json&country=Belgium&state=&street=&' + + 'limit=1&postalCode=', + match_querystring=True, + json=[{}] + )) + + self.partner_id_not_known.geo_localize() + + self.assertFalse( + self.partner_id_not_known.partner_longitude) + self.assertFalse( + self.partner_id_not_known.partner_latitude) diff --git a/geoengine_base_geolocalize/__manifest__.py b/geoengine_base_geolocalize/__manifest__.py index e079e53607..aae4462031 100644 --- a/geoengine_base_geolocalize/__manifest__.py +++ b/geoengine_base_geolocalize/__manifest__.py @@ -1,42 +1,25 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# Author: Laurent Mignon -# Copyright (c) 2015 Acsone SA/NV (http://www.acsone.eu) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -{'name': 'Geospatial support for base_geolocalize', - 'version': '8.0.0.1.0', - 'category': 'GeoBI', - 'author': "ACSONE SA/NV, Odoo Community Association (OCA)", - 'license': 'AGPL-3', - 'website': 'http://www.acsone.eu', - 'depends': [ - 'base', - 'geoengine_partner', - 'base_geolocalize', - ], - 'external_dependencies': { - 'python': ['requests'], - }, - 'data': [ - 'views/res_partner_view.xml' - ], - 'installable': False, - 'application': True, - 'autoinstall': True, - 'active': False, - } +# Copyright 2015-2017 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + 'name': 'Geospatial support for base_geolocalize', + 'version': '10.0.1.0.0', + 'category': 'GeoBI', + 'author': "ACSONE SA/NV, Odoo Community Association (OCA)", + 'license': 'AGPL-3', + 'website': 'https://github.com/OCA/geospatial', + 'depends': [ + 'base', + 'geoengine_partner', + ], + 'external_dependencies': { + 'python': [ + 'requests' + ], + }, + 'data': [ + 'views/res_partner_view.xml' + ], + 'application': True, + 'autoinstall': True, +} diff --git a/geoengine_base_geolocalize/models/__init__.py b/geoengine_base_geolocalize/models/__init__.py index fcb3548e65..91fed54d40 100644 --- a/geoengine_base_geolocalize/models/__init__.py +++ b/geoengine_base_geolocalize/models/__init__.py @@ -1,21 +1 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Laurent Mignon -# Copyright (c) 2015 Acsone SA/NV (http://www.acsone.eu) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## from . import res_partner diff --git a/geoengine_base_geolocalize/models/res_partner.py b/geoengine_base_geolocalize/models/res_partner.py index ef5f06eb23..858fedfc76 100644 --- a/geoengine_base_geolocalize/models/res_partner.py +++ b/geoengine_base_geolocalize/models/res_partner.py @@ -1,82 +1,19 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# Author: Laurent Mignon -# Copyright (c) 2015 Acsone SA/NV (http://www.acsone.eu) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# Copyright 2015 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -import logging -from odoo import api, fields -from odoo import exceptions -from odoo.tools.translate import _ +from odoo import api from odoo.addons.base_geoengine import geo_model from odoo.addons.base_geoengine import fields as geo_fields -try: - import requests -except ImportError: - logger = logging.getLogger(__name__) - logger.warning('requests is not available in the sys path') - -_logger = logging.getLogger(__name__) - class ResPartner(geo_model.GeoModel): """Add geo_point to partner using a function field""" _inherit = "res.partner" - @api.one - def geocode_address(self): - """Get the latitude and longitude by requesting "mapquestapi" - see http://open.mapquestapi.com/geocoding/ - """ - url = 'http://nominatim.openstreetmap.org/search' - pay_load = { - 'limit': 1, - 'format': 'json', - 'street': self.street or '', - 'postalCode': self.zip or '', - 'city': self.city or '', - 'state': self.state_id and self.state_id.name or '', - 'country': self.country_id and self.country_id.name or '', - 'countryCodes': self.country_id and self.country_id.code or ''} - - request_result = requests.get(url, params=pay_load) - try: - request_result.raise_for_status() - except Exception as e: - _logger.exception('Geocoding error') - raise exceptions.Warning(_( - 'Geocoding error. \n %s') % e.message) - vals = request_result.json() - vals = vals and vals[0] or {} - self.write({ - 'partner_latitude': vals.get('lat'), - 'partner_longitude': vals.get('lon'), - 'date_localization': fields.Date.today()}) - - @api.one - def geo_localize(self): - self.geocode_address() - return True - - @api.one + @api.multi @api.depends('partner_latitude', 'partner_longitude') - def _get_geo_point(self): + def _compute_geo_point(self): """ Set the `geo_point` of the partner depending of its `partner_latitude` and its `partner_longitude` @@ -84,11 +21,21 @@ def _get_geo_point(self): If one of those parameters is not set then reset the partner's geo_point and do not recompute it """ - if not self.partner_latitude or not self.partner_longitude: - self.geo_point = False - else: - self.geo_point = geo_fields.GeoPoint.from_latlon( - self.env.cr, self.partner_latitude, self.partner_longitude) + for rec in self: + if not rec.partner_latitude or not rec.partner_longitude: + rec.geo_point = False + else: + rec.geo_point = geo_fields.GeoPoint.from_latlon( + rec.env.cr, rec.partner_latitude, rec.partner_longitude) geo_point = geo_fields.GeoPoint( - readonly=True, store=True, compute='_get_geo_point') + store=True, compute='_compute_geo_point', inverse='_inverse_geo_point') + + @api.multi + def _inverse_geo_point(self): + for rec in self: + if not rec.geo_point: + rec.partner_longitude, rec.partner_latitude = False, False + else: + rec.partner_longitude, rec.partner_latitude = \ + geo_fields.GeoPoint.to_latlon(rec.env.cr, rec.geo_point) diff --git a/geoengine_base_geolocalize/tests/test_geoengine_partner.py b/geoengine_base_geolocalize/tests/test_geoengine_partner.py index 0c7c1887f1..75fdef1884 100644 --- a/geoengine_base_geolocalize/tests/test_geoengine_partner.py +++ b/geoengine_base_geolocalize/tests/test_geoengine_partner.py @@ -1,30 +1,11 @@ # -*- coding: utf-8 -*- -# -# -# Authors: Jonathan Nemry -# Copyright (c) 2015 Acsone SA/NV (http://www.acsone.eu) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# +# Copyright 2015-2017 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import odoo.tests.common as common class TestGeoenginePartner(common.TransactionCase): - def setUp(self): - common.TransactionCase.setUp(self) - def test_get_geo_point(self): partner_id = self.env.ref('base.user_root').partner_id partner_id.partner_longitude = False diff --git a/geoengine_base_geolocalize/views/res_partner_view.xml b/geoengine_base_geolocalize/views/res_partner_view.xml index 29d4190613..a535adcba8 100644 --- a/geoengine_base_geolocalize/views/res_partner_view.xml +++ b/geoengine_base_geolocalize/views/res_partner_view.xml @@ -1,16 +1,16 @@ - - - - geo_partner_form - res.partner - - - - - - - - - + + + + geo_partner_form + res.partner + + + + + + + + + diff --git a/geoengine_geoname_geocoder/res_partner.py b/geoengine_geoname_geocoder/res_partner.py index 9430ad5899..4f229a1007 100644 --- a/geoengine_geoname_geocoder/res_partner.py +++ b/geoengine_geoname_geocoder/res_partner.py @@ -107,7 +107,7 @@ def geocode_from_geonames(self, srid='900913', url = base_url + urlencode(filters) answer = urlopen(url) add.geo_point = self._get_point_from_reply(answer) - except Exception, exc: + except Exception as exc: logger.exception('error while updating geocodes') if strict: raise except_orm(_('Geoencoding fails'), str(exc)) diff --git a/setup/base_geolocalize_openstreetmap/.eggs/README.txt b/setup/base_geolocalize_openstreetmap/.eggs/README.txt new file mode 100644 index 0000000000..5d01668824 --- /dev/null +++ b/setup/base_geolocalize_openstreetmap/.eggs/README.txt @@ -0,0 +1,6 @@ +This directory contains eggs that were downloaded by setuptools to build, test, and run plug-ins. + +This directory caches those eggs to prevent repeated downloads. + +However, it is safe to delete this directory. + diff --git a/setup/base_geolocalize_openstreetmap/odoo/__init__.py b/setup/base_geolocalize_openstreetmap/odoo/__init__.py new file mode 100644 index 0000000000..de40ea7ca0 --- /dev/null +++ b/setup/base_geolocalize_openstreetmap/odoo/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/setup/base_geolocalize_openstreetmap/odoo/addons/__init__.py b/setup/base_geolocalize_openstreetmap/odoo/addons/__init__.py new file mode 100644 index 0000000000..de40ea7ca0 --- /dev/null +++ b/setup/base_geolocalize_openstreetmap/odoo/addons/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/setup/base_geolocalize_openstreetmap/odoo/addons/base_geolocalize_openstreetmap b/setup/base_geolocalize_openstreetmap/odoo/addons/base_geolocalize_openstreetmap new file mode 120000 index 0000000000..6076629b9a --- /dev/null +++ b/setup/base_geolocalize_openstreetmap/odoo/addons/base_geolocalize_openstreetmap @@ -0,0 +1 @@ +../../../../base_geolocalize_openstreetmap/ \ No newline at end of file diff --git a/setup/base_geolocalize_openstreetmap/setup.py b/setup/base_geolocalize_openstreetmap/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/base_geolocalize_openstreetmap/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/geoengine_base_geolocalize/.eggs/README.txt b/setup/geoengine_base_geolocalize/.eggs/README.txt new file mode 100644 index 0000000000..5d01668824 --- /dev/null +++ b/setup/geoengine_base_geolocalize/.eggs/README.txt @@ -0,0 +1,6 @@ +This directory contains eggs that were downloaded by setuptools to build, test, and run plug-ins. + +This directory caches those eggs to prevent repeated downloads. + +However, it is safe to delete this directory. + diff --git a/setup/geoengine_base_geolocalize/odoo/__init__.py b/setup/geoengine_base_geolocalize/odoo/__init__.py new file mode 100644 index 0000000000..de40ea7ca0 --- /dev/null +++ b/setup/geoengine_base_geolocalize/odoo/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/setup/geoengine_base_geolocalize/odoo/addons/__init__.py b/setup/geoengine_base_geolocalize/odoo/addons/__init__.py new file mode 100644 index 0000000000..de40ea7ca0 --- /dev/null +++ b/setup/geoengine_base_geolocalize/odoo/addons/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/setup/geoengine_base_geolocalize/odoo/addons/geoengine_base_geolocalize b/setup/geoengine_base_geolocalize/odoo/addons/geoengine_base_geolocalize new file mode 120000 index 0000000000..01e6adc904 --- /dev/null +++ b/setup/geoengine_base_geolocalize/odoo/addons/geoengine_base_geolocalize @@ -0,0 +1 @@ +../../../../geoengine_base_geolocalize/ \ No newline at end of file diff --git a/setup/geoengine_base_geolocalize/setup.py b/setup/geoengine_base_geolocalize/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/geoengine_base_geolocalize/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)