From 84631417e43c06e6c8d793239407b51c98cb9160 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Sat, 21 Aug 2021 12:49:30 +0200 Subject: [PATCH] [IMP] l10n_nl_postcodeapi: improved code and compatibility --- l10n_nl_postcodeapi/README.rst | 30 +- l10n_nl_postcodeapi/__manifest__.py | 11 +- .../data/ir_config_parameter.xml | 2 - .../examples/res.country.state.csv | 13 - l10n_nl_postcodeapi/i18n/nl.po | 3 - l10n_nl_postcodeapi/i18n/nl_NL.po | 50 --- l10n_nl_postcodeapi/models/__init__.py | 3 +- .../models/ir_config_parameter.py | 66 ++-- .../models/res_country_state.py | 39 --- l10n_nl_postcodeapi/models/res_partner.py | 106 +++--- l10n_nl_postcodeapi/readme/CONFIGURE.rst | 3 +- l10n_nl_postcodeapi/readme/CONTRIBUTORS.rst | 1 + l10n_nl_postcodeapi/readme/HISTORY.rst | 11 + l10n_nl_postcodeapi/readme/INSTALL.rst | 3 + .../static/description/index.html | 56 ++- .../tests/test_l10n_nl_postcodeapi.py | 325 ++++++++---------- 16 files changed, 329 insertions(+), 393 deletions(-) delete mode 100644 l10n_nl_postcodeapi/examples/res.country.state.csv delete mode 100644 l10n_nl_postcodeapi/i18n/nl_NL.po delete mode 100644 l10n_nl_postcodeapi/models/res_country_state.py create mode 100644 l10n_nl_postcodeapi/readme/HISTORY.rst diff --git a/l10n_nl_postcodeapi/README.rst b/l10n_nl_postcodeapi/README.rst index 74cbb0b16..17838a8a3 100644 --- a/l10n_nl_postcodeapi/README.rst +++ b/l10n_nl_postcodeapi/README.rst @@ -45,6 +45,9 @@ Installation This module depends on the standard Odoo module base_address_extended, which will split up the street field into separate fields for street name and number. +It now also depends on l10n_nl_country_states, to provide the names of the provinces, +that will be added to the res_country_state model. + You also need to have the 'pyPostcode' Python library by Stefan Jansen installed (https://pypi.python.org/pypi/pyPostcode). @@ -55,8 +58,22 @@ Please enter the API key that you request from PostcodeAPI into the system parameter 'l10n_nl_postcodeapi.apikey' Provinces are auto-completed if a country state with the exact name is found in -the system. A CSV file with the Dutch provinces is included in the data -directory, but not loaded by default. You can import the file manually. +the system. + +Changelog +========= + +11.0.2.0.0 (2021-08-21) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Now depend on l10n_nl_country_states to prevent conflicts with that module; +- No manual caching of province data. It complicates code and performance gain + will almost certainly be negligible; +- Check valid Api Key in configuration immediately when setting or updating key; +- Take into account that with l10n_nl_country_states installed, state_id on partner + will in many cases be set, even if postcode api not active, or cannot find + address; +- Adjust tests to run properly on databases already containing data. Bug Tracker =========== @@ -81,6 +98,7 @@ Contributors * Stefan Rijnhart (Therp BV) * Andrea Stirpe +* Ronald Portier Maintainers ~~~~~~~~~~~ @@ -95,6 +113,14 @@ 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. +.. |maintainer-NL66278| image:: https://github.com/NL66278.png?size=40px + :target: https://github.com/NL66278 + :alt: NL66278 + +Current `maintainer `__: + +|maintainer-NL66278| + This module is part of the `OCA/l10n-netherlands `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_nl_postcodeapi/__manifest__.py b/l10n_nl_postcodeapi/__manifest__.py index 597d0e9ad..1f224ead9 100644 --- a/l10n_nl_postcodeapi/__manifest__.py +++ b/l10n_nl_postcodeapi/__manifest__.py @@ -1,15 +1,18 @@ -# Copyright 2013-2015 Therp BV +# Copyright 2013-2021 Therp BV # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - { 'name': 'Integration with PostcodeApi.nu', 'summary': 'Autocomplete Dutch addresses using PostcodeApi.nu', - 'version': '11.0.1.0.0', + 'version': '11.0.2.0.0', 'author': 'Therp BV,Odoo Community Association (OCA)', 'category': 'Localization', 'website': 'https://github.com/OCA/l10n-netherlands', + 'maintainers': ['NL66278'], # ronald@therp.nl 'license': 'AGPL-3', - 'depends': ['base_address_extended'], + 'depends': [ + 'base_address_extended', + 'l10n_nl_country_states', + ], 'data': [ 'data/ir_config_parameter.xml', ], diff --git a/l10n_nl_postcodeapi/data/ir_config_parameter.xml b/l10n_nl_postcodeapi/data/ir_config_parameter.xml index fcf2c93fe..80fa6dd15 100644 --- a/l10n_nl_postcodeapi/data/ir_config_parameter.xml +++ b/l10n_nl_postcodeapi/data/ir_config_parameter.xml @@ -1,9 +1,7 @@ - l10n_nl_postcodeapi.apikey Your API key - diff --git a/l10n_nl_postcodeapi/examples/res.country.state.csv b/l10n_nl_postcodeapi/examples/res.country.state.csv deleted file mode 100644 index 3e96051cd..000000000 --- a/l10n_nl_postcodeapi/examples/res.country.state.csv +++ /dev/null @@ -1,13 +0,0 @@ -id,country_id,name,code -,nl,Drenthe,DR -,nl,Flevoland,FL -,nl,Friesland,FR -,nl,Gelderland,GD -,nl,Groningen,GR -,nl,Limburg,LB -,nl,Noord-Brabant,NB -,nl,Noord-Holland,NH -,nl,Overijssel,OV -,nl,Utrecht,UT -,nl,Zeeland,ZH -,nl,Zuid-Holland,ZL diff --git a/l10n_nl_postcodeapi/i18n/nl.po b/l10n_nl_postcodeapi/i18n/nl.po index d1630c66e..035e8539f 100644 --- a/l10n_nl_postcodeapi/i18n/nl.po +++ b/l10n_nl_postcodeapi/i18n/nl.po @@ -51,6 +51,3 @@ msgstr "" #: model:ir.model,name:l10n_nl_postcodeapi.model_ir_config_parameter msgid "ir.config_parameter" msgstr "ir.config_parameter" - -#~ msgid "Partner" -#~ msgstr "Relatie" diff --git a/l10n_nl_postcodeapi/i18n/nl_NL.po b/l10n_nl_postcodeapi/i18n/nl_NL.po deleted file mode 100644 index 1ef27d43b..000000000 --- a/l10n_nl_postcodeapi/i18n/nl_NL.po +++ /dev/null @@ -1,50 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * l10n_nl_postcodeapi -# -# Translators: -# Peter Hageman , 2017 -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-21 08:04+0000\n" -"PO-Revision-Date: 2017-06-21 08:04+0000\n" -"Last-Translator: Peter Hageman , 2017\n" -"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/" -"teams/23907/nl_NL/)\n" -"Language: nl_NL\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: l10n_nl_postcodeapi -#: model:ir.model,name:l10n_nl_postcodeapi.model_res_partner -msgid "Contact" -msgstr "" - -#. module: l10n_nl_postcodeapi -#: code:addons/l10n_nl_postcodeapi/models/res_partner.py:25 -#, python-format -msgid "" -"Could not verify the connection with the address lookup service (if you want " -"to get rid of this message, please rename or delete the system parameter " -"'l10n_nl_postcodeapi.apikey')." -msgstr "" - -#. module: l10n_nl_postcodeapi -#: model:ir.model,name:l10n_nl_postcodeapi.model_res_country_state -msgid "Country state" -msgstr "" - -#. module: l10n_nl_postcodeapi -#: code:addons/l10n_nl_postcodeapi/models/res_partner.py:63 -#, python-format -msgid "Warning" -msgstr "" - -#. module: l10n_nl_postcodeapi -#: model:ir.model,name:l10n_nl_postcodeapi.model_ir_config_parameter -msgid "ir.config_parameter" -msgstr "ir.config_parameter" diff --git a/l10n_nl_postcodeapi/models/__init__.py b/l10n_nl_postcodeapi/models/__init__.py index 295e00288..28cf16adf 100644 --- a/l10n_nl_postcodeapi/models/__init__.py +++ b/l10n_nl_postcodeapi/models/__init__.py @@ -1,5 +1,4 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - +"""Import models for module.""" from . import res_partner from . import ir_config_parameter -from . import res_country_state diff --git a/l10n_nl_postcodeapi/models/ir_config_parameter.py b/l10n_nl_postcodeapi/models/ir_config_parameter.py index c2186ba6b..b7c242913 100644 --- a/l10n_nl_postcodeapi/models/ir_config_parameter.py +++ b/l10n_nl_postcodeapi/models/ir_config_parameter.py @@ -1,33 +1,59 @@ -# Copyright 2013-2015 Therp BV +# Copyright 2013-2021 Therp BV # @autors: Stefan Rijnhart, Ronald Portier # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Interface to the configured (or not) Postcode API.""" +# pylint: disable=protected-access +try: + import pyPostcode +except ImportError: + pyPostcode = None -from odoo import api, models +from odoo.exceptions import UserError +from odoo import api, models, _ +from odoo.tools import ormcache class IrConfigParameter(models.Model): + """Interface to the configured (or not) Postcode API.""" _inherit = 'ir.config_parameter' + @api.model + @ormcache(skiparg=2) + def get_provider_obj(self): + """get Api to interface with Dutch postcode provider.""" + if not pyPostcode: + # Module not loaded. + return None # pragma: no cover + apikey = self.sudo().get_param('l10n_nl_postcodeapi.apikey', '').strip() + if not apikey or apikey == 'Your API key': + return None + provider_obj = pyPostcode.Api(apikey, (2, 0, 0)) + test = provider_obj.getaddress('1053NJ', '334T') + if not test or not test._data: + raise UserError(_( + 'Could not verify the connection with the address lookup service' + ' (if you want to get rid of this message, please rename or delete' + ' the system parameter \'l10n_nl_postcodeapi.apikey\').' + )) + return provider_obj + @api.model def create(self, vals): - """ - Clear the postcode provider cache when the API - key is created - """ - if vals.get('key') == 'l10n_nl_postcodeapi.apikey': - partner_obj = self.env['res.partner'] - partner_obj.get_provider_obj.clear_cache(partner_obj) - return super(IrConfigParameter, self).create(vals) + """Clear the postcode provider cache when the API key is created.""" + new_record = super().create(vals) + new_record._check_and_reset_provider() + return new_record @api.multi def write(self, vals): - """ - Clear the postcode provider cache when the API - key is modified - """ - key = 'l10n_nl_postcodeapi.apikey' - if (vals.get('key') == key or - self.search([('id', 'in', self.ids), ('key', '=', key)])): - partner_obj = self.env['res.partner'] - partner_obj.get_provider_obj.clear_cache(partner_obj) - return super(IrConfigParameter, self).write(vals) + """Clear the postcode provider cache when the API key is modified.""" + result = super().write(vals) + self._check_and_reset_provider() + return result + + def _check_and_reset_provider(self): + """Clear provider cache and check wether key valid.""" + for this in self: + if this.key == 'l10n_nl_postcodeapi.apikey': + this.get_provider_obj.clear_cache(this) + this.get_provider_obj() diff --git a/l10n_nl_postcodeapi/models/res_country_state.py b/l10n_nl_postcodeapi/models/res_country_state.py deleted file mode 100644 index 2a523996f..000000000 --- a/l10n_nl_postcodeapi/models/res_country_state.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2013-2015 Therp BV -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo import api, models - - -class ResCountryState(models.Model): - _inherit = 'res.country.state' - - @api.multi - def write(self, vals): - """ - Clear the postcode provider cache when the state - table is altered. - """ - self.env['res.partner'].get_province.clear_cache( - self.env['res.partner']) - return super(ResCountryState, self).write(vals) - - @api.model - @api.returns('self', lambda value: value.id) - def create(self, vals): - """ - Clear the postcode provider cache when the state - table is altered. - """ - self.env['res.partner'].get_province.clear_cache( - self.env['res.partner']) - return super(ResCountryState, self).create(vals) - - @api.multi - def unlink(self): - """ - Clear the postcode provider cache when the state - table is altered. - """ - self.env['res.partner'].get_province.clear_cache( - self.env['res.partner']) - return super(ResCountryState, self).unlink() diff --git a/l10n_nl_postcodeapi/models/res_partner.py b/l10n_nl_postcodeapi/models/res_partner.py index f435cebb5..88003bd82 100644 --- a/l10n_nl_postcodeapi/models/res_partner.py +++ b/l10n_nl_postcodeapi/models/res_partner.py @@ -1,72 +1,70 @@ -# Copyright 2013-2015 Therp BV +# Copyright 2013-2021 Therp BV # @autors: Stefan Rijnhart, Ronald Portier # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Interface with Dutch Postcode API service.""" +# pylint: disable=protected-access +import logging -from odoo import api, models, _ -from odoo.tools import ormcache +from odoo import _, api, models + +_logger = logging.getLogger(__name__) class ResPartner(models.Model): + """Interface with Dutch Postcode API service.""" _inherit = 'res.partner' @api.model - @ormcache(skiparg=2) - def get_provider_obj(self): - apikey = self.env['ir.config_parameter'].sudo().get_param( - 'l10n_nl_postcodeapi.apikey', '').strip() - if not apikey or apikey == 'Your API key': - return False - from pyPostcode import Api - return Api(apikey, (2, 0, 0)) - - def _postcodeapi_check_valid_provider(self, provider): - test = provider.getaddress('1053NJ', '334T') - if not test or not test._data: - return _('Could not verify the connection with the address ' - 'lookup service (if you want to get rid of this ' - 'message, please rename or delete the system parameter ' - '\'l10n_nl_postcodeapi.apikey\').') - return None - - @api.model - @ormcache(skiparg=2) - def get_province(self, province): - """ Return the province or empty recordset """ - if not province: - return self.env['res.country.state'] - return self.env['res.country.state'].search([ - ('name', '=', province) - ], limit=1) + def get_country_state(self, country, state_name): + """Lookup state within a country.""" + state_model = self.env['res.country.state'] + if not country or not state_name: + return state_model + return state_model.search( + [ + ('country_id', '=', country.id), + ('name', '=', state_name), + ], + limit=1 + ) @api.onchange('zip', 'street_number', 'country_id') def on_change_zip_street_number(self): - """ - Normalize the zip code, check on the partner's country and - if all is well, request address autocompletion data. + """Autocomplete dutch addresses if postalcode and streetnumber are filled. NB. postal_code is named 'zip' in Odoo, but is this a reserved - keyword in Python + keyword in Python. """ - postal_code = self.zip and self.zip.replace(' ', '') + country_nl = self.env.ref('base.nl') country = self.country_id - if not (postal_code and self.street_number) or \ - country and country != self.env.ref('base.nl'): - return {} - - provider_obj = self.get_provider_obj() + if country and country != country_nl: + # Do not check for postal code outside of the Netherlands. + return + postal_code = self.zip and self.zip.replace(' ', '') + if not postal_code or not self.street_number: + # Only check if both postal code and street number have been filled. + return + parameter_model = self.env["ir.config_parameter"] + provider_obj = parameter_model.get_provider_obj() if not provider_obj: - return {} - err_msg = self._postcodeapi_check_valid_provider(provider_obj) - if err_msg: - return { - 'warning': { - 'title': _("Warning"), - 'message': err_msg, - } - } + # Do not check when we can not use API. + return # pragma: no cover pc_info = provider_obj.getaddress(postal_code, self.street_number) - if not pc_info or not pc_info._data: - return {} - self.street_name = pc_info.street - self.city = pc_info.town - self.state_id = self.get_province(pc_info.province) + if not pc_info or not pc_info._data: # pragma: no cover + # Should not really happen in the Netherlands. + _logger.warning( + _( + "No address found for partner %s, with postalcode %s and" + " housenumber %d." + ), + self.display_name, + postal_code, + self.street_number + ) + return + vals = { + "street_name": pc_info.street, + "city": pc_info.town, + "state_id": self.get_country_state(country_nl, pc_info.province).id + } + self.update(vals) diff --git a/l10n_nl_postcodeapi/readme/CONFIGURE.rst b/l10n_nl_postcodeapi/readme/CONFIGURE.rst index 8b9c9b008..e28a2f4db 100644 --- a/l10n_nl_postcodeapi/readme/CONFIGURE.rst +++ b/l10n_nl_postcodeapi/readme/CONFIGURE.rst @@ -2,5 +2,4 @@ Please enter the API key that you request from PostcodeAPI into the system parameter 'l10n_nl_postcodeapi.apikey' Provinces are auto-completed if a country state with the exact name is found in -the system. A CSV file with the Dutch provinces is included in the data -directory, but not loaded by default. You can import the file manually. +the system. diff --git a/l10n_nl_postcodeapi/readme/CONTRIBUTORS.rst b/l10n_nl_postcodeapi/readme/CONTRIBUTORS.rst index e43ef2540..a6f1477f1 100644 --- a/l10n_nl_postcodeapi/readme/CONTRIBUTORS.rst +++ b/l10n_nl_postcodeapi/readme/CONTRIBUTORS.rst @@ -1,2 +1,3 @@ * Stefan Rijnhart (Therp BV) * Andrea Stirpe +* Ronald Portier diff --git a/l10n_nl_postcodeapi/readme/HISTORY.rst b/l10n_nl_postcodeapi/readme/HISTORY.rst new file mode 100644 index 000000000..9a459c9f4 --- /dev/null +++ b/l10n_nl_postcodeapi/readme/HISTORY.rst @@ -0,0 +1,11 @@ +11.0.2.0.0 (2021-08-21) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Now depend on l10n_nl_country_states to prevent conflicts with that module; +- No manual caching of province data. It complicates code and performance gain + will almost certainly be negligible; +- Check valid Api Key in configuration immediately when setting or updating key; +- Take into account that with l10n_nl_country_states installed, state_id on partner + will in many cases be set, even if postcode api not active, or cannot find + address; +- Adjust tests to run properly on databases already containing data. diff --git a/l10n_nl_postcodeapi/readme/INSTALL.rst b/l10n_nl_postcodeapi/readme/INSTALL.rst index 36b8b301c..b8c262bd5 100644 --- a/l10n_nl_postcodeapi/readme/INSTALL.rst +++ b/l10n_nl_postcodeapi/readme/INSTALL.rst @@ -1,5 +1,8 @@ This module depends on the standard Odoo module base_address_extended, which will split up the street field into separate fields for street name and number. +It now also depends on l10n_nl_country_states, to provide the names of the provinces, +that will be added to the res_country_state model. + You also need to have the 'pyPostcode' Python library by Stefan Jansen installed (https://pypi.python.org/pypi/pyPostcode). diff --git a/l10n_nl_postcodeapi/static/description/index.html b/l10n_nl_postcodeapi/static/description/index.html index 52ece9436..26849bdbb 100644 --- a/l10n_nl_postcodeapi/static/description/index.html +++ b/l10n_nl_postcodeapi/static/description/index.html @@ -378,34 +378,55 @@

Integration with PostcodeApi.nu

Table of contents

-

Installation

+

Installation

This module depends on the standard Odoo module base_address_extended, which will split up the street field into separate fields for street name and number.

+

It now also depends on l10n_nl_country_states, to provide the names of the provinces, +that will be added to the res_country_state model.

You also need to have the ‘pyPostcode’ Python library by Stefan Jansen installed (https://pypi.python.org/pypi/pyPostcode).

-

Configuration

+

Configuration

Please enter the API key that you request from PostcodeAPI into the system parameter ‘l10n_nl_postcodeapi.apikey’

Provinces are auto-completed if a country state with the exact name is found in -the system. A CSV file with the Dutch provinces is included in the data -directory, but not loaded by default. You can import the file manually.

+the system.

+
+
+

Changelog

+
+

11.0.2.0.0 (2021-08-21)

+
    +
  • Now depend on l10n_nl_country_states to prevent conflicts with that module;
  • +
  • No manual caching of province data. It complicates code and performance gain +will almost certainly be negligible;
  • +
  • Check valid Api Key in configuration immediately when setting or updating key;
  • +
  • Take into account that with l10n_nl_country_states installed, state_id on partner +will in many cases be set, even if postcode api not active, or cannot find +address;
  • +
  • Adjust tests to run properly on databases already containing data.
  • +
+
-

Bug Tracker

+

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 smashing it by providing a detailed and welcomed @@ -413,27 +434,30 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Therp BV
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

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.

+

Current maintainer:

+

NL66278

This module is part of the OCA/l10n-netherlands project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/l10n_nl_postcodeapi/tests/test_l10n_nl_postcodeapi.py b/l10n_nl_postcodeapi/tests/test_l10n_nl_postcodeapi.py index e5171647b..4bdff8f99 100644 --- a/l10n_nl_postcodeapi/tests/test_l10n_nl_postcodeapi.py +++ b/l10n_nl_postcodeapi/tests/test_l10n_nl_postcodeapi.py @@ -1,232 +1,185 @@ -# Copyright 2018-2020 Onestein () +# Copyright 2018-2020 Onestein . +# Copyright 2021 Therp BV . +# With inspiration from: https://realpython.com/testing-third-party-apis-with-mocks/ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Test the postcode api using a mock service.""" +# pylint: disable=protected-access,unused-argument +from mock import patch -try: - from unittest.mock import patch -except ImportError: - from mock import patch +from pyPostcode import ResourceV2 -from odoo.modules.module import get_module_resource -from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError +from odoo.tests.common import SavepointCase -class TestNlPostcodeapi(TransactionCase): +class TestPostcodeApi(SavepointCase): + """Test the postcode api using a mock service.""" - def setUp(self): - super(TestNlPostcodeapi, self).setUp() - - # this block of code removes the existing provinces - # eventually already created by module l10n_nl_country_states - # to avoid conflicts with tests of l10n_nl_country_states - is_l10n_nl_country_states_installed = self.env['ir.model']._get( - 'res.country.state.nl.zip' + @classmethod + def setUpClass(cls): + """Setup test.""" + super().setUpClass() + # Get the pyPostcode.Api from the actual class where used. + path_addon = 'odoo.addons.l10n_nl_postcodeapi.' + path_file = 'models.ir_config_parameter.' + cls.api_handler = path_addon + path_file + cls.mock_getaddress_patcher = patch( + cls.api_handler + 'pyPostcode.Api.getaddress' ) - self.country_nl = self.env['res.country'].search([ - ('code', 'like', 'NL') - ], limit=1) - self.assertTrue(self.country_nl) - if is_l10n_nl_country_states_installed: - NlZipStateModel = self.env['res.country.state.nl.zip'] - NlZipStateModel.search([]).unlink() - states = self.env['res.country.state'].search([ - ('country_id', '=', self.country_nl.id) - ]) - states.unlink() - states = self.env['res.country.state'].search([ - ('country_id', '=', self.country_nl.id) - ]) - states.unlink() - - def load_nl_provinces(self): - csv_resource = get_module_resource( - 'l10n_nl_postcodeapi', - 'examples', - 'res.country.state.csv', + cls.mock_getaddress = cls.mock_getaddress_patcher.start() + cls.country_nl = cls.env.ref('base.nl') + cls.config_parameter = cls.env.ref('l10n_nl_postcodeapi.parameter_apikey') + # Make sure there is some value, in the key, otherwise api will not run. + cls.config_parameter.write({'value': 'Some random value'}) + + @classmethod + def tearDownClass(cls): + """Unpatch API.""" + cls.mock_getaddress = cls.mock_getaddress_patcher.stop() + super().tearDownClass() + + def test_ir_config_parameter(self): + """Test setting of configuration parameter.""" + # Verify l10n_nl_postcodeapi.apikey is created. + self.assertTrue(self.config_parameter) + # Setting apikey to invalid value should result in Exception. + with self.assertRaises(UserError): + self.mock_getaddress.return_value = False + self.config_parameter.write({'value': 'KEYXXXXXXXXXXXNOTVALID'}) + + def test_orm_cache(self): + """Repeated calls to get_provider_obj should just return existing value.""" + self.mock_getaddress.return_value = ResourceV2( + { + "postcode": 'test 1053NJ', + "house_number": '334T', + "street": "Jacob van Lennepkade", + "town": "Amsterdam", + "province": {"id": 20, "label": "Noord-Holland"}, + } + ) + parameter_model = self.env["ir.config_parameter"] + parameter_model.get_provider_obj() + saved_call_count = self.mock_getaddress.call_count + # Call again... + parameter_model.get_provider_obj() + self.assertEqual(saved_call_count, self.mock_getaddress.call_count) + # Writing new key should cause extra call. + self.config_parameter.write({'value': 'Another random value'}) + self.assertEqual( + 1, + self.mock_getaddress.call_count - saved_call_count ) - csv_file = open(csv_resource, 'rb').read() - import_wizard = self.env['base_import.import'].create({ - 'res_model': 'res.country.state', - 'file': csv_file, - 'file_type': 'text/csv' - }) - - result = import_wizard.parse_preview({ - 'quoting': '"', - 'separator': ',', - 'headers': True, - }) - self.assertIsNone(result.get('error')) - results = import_wizard.do( - ['id', 'country_id', 'name', 'code'], - {'headers': True, 'separator': ',', 'quoting': '"'}) - self.assertFalse( - results, "results should be empty on successful import") - - def test_01_ir_config_parameter(self): - config_parameter = self.env['ir.config_parameter'].search([ - ('key', '=', 'l10n_nl_postcodeapi.apikey') - ]) - # Verify l10n_nl_postcodeapi.apikey is created - self.assertTrue(config_parameter) - self.assertEqual(config_parameter.value, 'Your API key') - - # Verify l10n_nl_postcodeapi.apikey is modified - config_parameter.write({ - 'value': 'KEYXXXXXXXXXXXNOTVALID' - }) - self.assertEqual(config_parameter.value, 'KEYXXXXXXXXXXXNOTVALID') - - def test_02_res_country_state(self): - - # Load res.country.state.csv - self.load_nl_provinces() - - # Verify res.country.state created - states = self.env['res.country.state'].search([ - ('country_id', '=', self.country_nl.id) - ]) - self.assertTrue(states) - - # Verify res.country.state modified - states[0].write({ - 'name': 'test' - }) - self.assertEqual(states[0].name, 'test') - - # Verify res.country.state unlinked - states[0].unlink() - test_states = self.env['res.country.state'].search([ - ('name', 'like', 'test') - ]) - self.assertFalse(test_states) - def test_03_res_partner_with_province(self): - # Set l10n_nl_postcodeapi.apikey - config_parameter = self.env['ir.config_parameter'].search([ - ('key', '=', 'l10n_nl_postcodeapi.apikey') - ]) - config_parameter.write({ - 'value': 'DZyipS65BT6n52jQHpVXs53r4bYK8yng3QWQT2tV' + def test_res_partner_with_province(self): + """Test setting partner with state/province.""" + # Create In Memory partner (no actual db update). + partner = self.env['res.partner'].new({ + 'name': 'test partner', + 'country_id': self.country_nl.id, + 'street_number': '10', + 'zip': 'test 4811DJ', }) - - def _patched_api_connector(*args, **kwargs): - return False - - def _patched_api_get_address(*args, **kwargs): - address = { - "_data": "test", + self.mock_getaddress.return_value = ResourceV2( + { + "postcode": 'test 4811DJ', + "house_number": '10', "street": "Claudius Prinsenlaan", "town": "Breda", - "province": "Noord-Brabant" + "province": {"id": 1, "label": "Noord-Brabant"}, } - return type('Address', tuple(), address)() - - patch_api_connector = patch( - 'odoo.addons.l10n_nl_postcodeapi.models.res_partner.ResPartner.' - '_postcodeapi_check_valid_provider', _patched_api_connector) - patch_api_get_address = patch( - 'pyPostcode.Api.getaddress', _patched_api_get_address) - patch_api_connector.start() - patch_api_get_address.start() + ) + partner.on_change_zip_street_number() + self.assertEqual(partner.street_name, 'Claudius Prinsenlaan') + self.assertEqual(partner.city, 'Breda') + self.assertEqual(partner.state_id.name, 'Noord-Brabant') + self.assertEqual(partner.state_id.code, 'NL-NB') - # Load res.country.state.csv - self.load_nl_provinces() + def test_res_partner_no_province(self): + """Test setting partner with postalcode not linked to province. - partner = self.env['res.partner'].create({ + Province should not be filled. That is because we do not create a partner in + the database, but only in memory. So the logic that assigns a province + based on the ranges of zip-code for each province, part of the module + l10n_nl_contry_states, is not called. + """ + partner = self.env['res.partner'].new({ 'name': 'test partner', 'country_id': self.country_nl.id, 'street_number': '10', - 'zip': 'test 4811dj', + 'state_id': False, + 'zip': '1018BC', }) - res = partner.on_change_zip_street_number() - self.assertFalse(res) - - patch_api_get_address.stop() - patch_api_connector.stop() - - partner._convert_to_write(partner._cache) - self.assertEqual(partner.street_name, 'Claudius Prinsenlaan') - self.assertEqual(partner.city, 'Breda') - self.assertEqual(partner.state_id.name, 'Noord-Brabant') - self.assertEqual(partner.state_id.code, 'NB') - self.assertEqual(partner.street, 'Claudius Prinsenlaan 10') - - def test_04_res_partner_no_province(self): - # Set l10n_nl_postcodeapi.apikey - config_parameter = self.env['ir.config_parameter'].search([ - ('key', '=', 'l10n_nl_postcodeapi.apikey') - ]) - config_parameter.write({ - 'value': 'DZyipS65BT6n52jQHpVXs53r4bYK8yng3QWQT2tV' - }) - - def _patched_api_connector(*args, **kwargs): - return False - - def _patched_api_get_address(*args, **kwargs): - address = { - "_data": "test", - "street": "Claudius Prinsenlaan", - "town": "Breda", - "province": "Noord-Brabant" + self.mock_getaddress.return_value = ResourceV2( + { + "postcode": '1018BC', + "house_number": '10', + "street": "Blankenstraat", + "town": "Amsterdam", + "province": False, } - return type('Address', tuple(), address)() - - patch_api_connector = patch( - 'odoo.addons.l10n_nl_postcodeapi.models.res_partner.ResPartner.' - '_postcodeapi_check_valid_provider', _patched_api_connector) - patch_api_get_address = patch( - 'pyPostcode.Api.getaddress', _patched_api_get_address) - patch_api_connector.start() - patch_api_get_address.start() - - partner = self.env['res.partner'].create({ + ) + partner.on_change_zip_street_number() + self.assertEqual(partner.street_name, 'Blankenstraat') + self.assertEqual(partner.city, 'Amsterdam') + self.assertEqual(partner.state_id.name, False) + + def test_res_partner_incomplete_information(self): + """Test on_change for partner in Netherlands without postalcode.""" + partner = self.env['res.partner'].new({ 'name': 'test partner', 'country_id': self.country_nl.id, 'street_number': '10', - 'zip': '4811dj', }) - res = partner.on_change_zip_street_number() - self.assertFalse(res) - - patch_api_get_address.stop() - patch_api_connector.stop() - - partner._convert_to_write(partner._cache) - self.assertEqual(partner.street_name, 'Claudius Prinsenlaan') - self.assertEqual(partner.city, 'Breda') + self.mock_getaddress.return_value = False + partner.on_change_zip_street_number() + self.assertEqual(partner.street_number, '10') + self.assertFalse(partner.street_name) + self.assertFalse(partner.city) self.assertFalse(partner.state_id) - self.assertEqual(partner.street, 'Claudius Prinsenlaan 10') - def test_05_res_partner_other_country(self): + def test_res_partner_other_country(self): + """Test on_change for partner in another country.""" country_it = self.env['res.country'].search([ ('code', 'like', 'IT') ], limit=1) - partner = self.env['res.partner'].create({ + partner = self.env['res.partner'].new({ 'name': 'test partner', 'country_id': country_it.id, 'street_number': '10', 'zip': '4811dj', }) - res = partner.on_change_zip_street_number() - self.assertFalse(res) - partner._convert_to_write(partner._cache) + partner.on_change_zip_street_number() + self.assertEqual(partner.street_number, '10') self.assertFalse(partner.street_name) self.assertFalse(partner.city) self.assertFalse(partner.state_id) - self.assertEqual(partner.street, '10') - - def test_06_res_partner_no_key(self): - partner = self.env['res.partner'].create({ + def test_res_partner_no_key(self): + """Test on_change while API key not set.""" + self.config_parameter.write({'value': 'Your API key'}) + partner = self.env['res.partner'].new({ 'name': 'test partner', 'street_number': '10', 'zip': '4811dj', 'country_id': self.country_nl.id, }) - res = partner.on_change_zip_street_number() - self.assertFalse(res) - partner._convert_to_write(partner._cache) + partner.on_change_zip_street_number() + self.assertEqual(partner.street_number, '10') self.assertFalse(partner.street_name) self.assertFalse(partner.city) self.assertFalse(partner.state_id) + + def test_get_country_state(self): + """Test get_country state with and without state_name.""" + state_brabant = self.env.ref("l10n_nl_country_states.state_noordbrabant") + partner_model = self.env['res.partner'] + # Call with full information. + state = partner_model.get_country_state(self.country_nl, "Noord-Brabant") + self.assertTrue(bool(state)) + self.assertEqual(state.name, state_brabant.name) + self.assertEqual(state.code, state_brabant.code) + # Call with missing province should return empty recordset. + state = partner_model.get_country_state(self.country_nl, False) + self.assertFalse(bool(state)) + self.assertFalse(state.id)