From 1b3d1c3fa6565e0b5067e0460bc9c49b3762fa8d Mon Sep 17 00:00:00 2001 From: Antonio Espinosa Date: Tue, 31 Mar 2015 00:51:36 +0200 Subject: [PATCH] [ADD] NUTS regions as a base addon --- base_location_nuts/README.rst | 91 ++++++++ base_location_nuts/__init__.py | 7 + base_location_nuts/__openerp__.py | 42 ++++ base_location_nuts/i18n/es.po | 220 ++++++++++++++++++ base_location_nuts/models/__init__.py | 7 + base_location_nuts/models/res_partner.py | 61 +++++ base_location_nuts/models/res_partner_nuts.py | 30 +++ .../security/ir.model.access.csv | 2 + .../static/description/icon.png | Bin 0 -> 7920 bytes .../views/res_partner_nuts_view.xml | 76 ++++++ base_location_nuts/views/res_partner_view.xml | 78 +++++++ base_location_nuts/wizard/__init__.py | 6 + base_location_nuts/wizard/nuts_import.py | 205 ++++++++++++++++ .../wizard/nuts_import_view.xml | 51 ++++ 14 files changed, 876 insertions(+) create mode 100644 base_location_nuts/README.rst create mode 100644 base_location_nuts/__init__.py create mode 100644 base_location_nuts/__openerp__.py create mode 100644 base_location_nuts/i18n/es.po create mode 100644 base_location_nuts/models/__init__.py create mode 100644 base_location_nuts/models/res_partner.py create mode 100644 base_location_nuts/models/res_partner_nuts.py create mode 100644 base_location_nuts/security/ir.model.access.csv create mode 100644 base_location_nuts/static/description/icon.png create mode 100644 base_location_nuts/views/res_partner_nuts_view.xml create mode 100644 base_location_nuts/views/res_partner_view.xml create mode 100644 base_location_nuts/wizard/__init__.py create mode 100644 base_location_nuts/wizard/nuts_import.py create mode 100644 base_location_nuts/wizard/nuts_import_view.xml diff --git a/base_location_nuts/README.rst b/base_location_nuts/README.rst new file mode 100644 index 000000000..18c380523 --- /dev/null +++ b/base_location_nuts/README.rst @@ -0,0 +1,91 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +============ +NUTS Regions +============ + +This module allows to import NUTS locations. + +Creates two new fields in Partner object: + +* Region (res.partner.region): Classification over state, automatically + calculated when state is selected +* Substate (res.partner.substate): Classification above state, user must select + one from available for selected state + + +Installation +============ + +You need to install another addon (one for each country) in order to use +these NUTS, for example: + +* l10n_es_location_nuts : + * Spanish Provinces (NUTS level 4) as Partner State + * Spanish Autonomous communities (NUTS level 3) as Partner Substate + * Spanish Regions (NUTS level 2) as Partner Region +* l10n_de_location_nuts : + * German states (NUTS level 2) as Partner State + * German districts (NUTS level 3) as Partner Substate + * German regions (NUTS level 4) as Partner Region + + +Configuration +============= + +After installation, you must click at import wizard to populate NUTS items +in Odoo database in: +Sales > Configuration > Address Book > Import NUTS 2013 + +This wizard will download from Europe RAMON service the metadata to +build NUTS in Odoo. Each localization addon (l10n_es_location_nuts, +l10n_de_location_nuts, ...) will inherit this wizard and +relate each NUTS item with states. So if you install a new localization addon +you must re-build NUTS clicking this wizard again. + + +Usage +===== + +Only Administrator can manage NUTS list (it is not neccesary because +it is an European convention) but any registered user can read them, +in order to allow to assign them to partner object. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/134/{branch} + + +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 feedback +`here `_. + + +Credits +======= + +Contributors +------------ + +* Rafael Blasco +* Antonio Espinosa + +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 http://odoo-community.org. \ No newline at end of file diff --git a/base_location_nuts/__init__.py b/base_location_nuts/__init__.py new file mode 100644 index 000000000..7333c0440 --- /dev/null +++ b/base_location_nuts/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in root directory +############################################################################## + +from . import models +from . import wizard diff --git a/base_location_nuts/__openerp__.py b/base_location_nuts/__openerp__.py new file mode 100644 index 000000000..a1aad7791 --- /dev/null +++ b/base_location_nuts/__openerp__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Python source code encoding : https://www.python.org/dev/peps/pep-0263/ +############################################################################## +# +# OpenERP, Odoo Source Management Solution +# Copyright (c) 2015 Antiun Ingeniería S.L. (http://www.antiun.com) +# Antonio Espinosa +# +# 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': 'NUTS Regions', + 'category': 'Localisation/Europe', + 'version': '8.0.1.0.0', + 'depends': [ + 'base', + ], + 'data': [ + 'views/res_partner_nuts_view.xml', + 'views/res_partner_view.xml', + 'wizard/nuts_import_view.xml', + 'security/ir.model.access.csv', + ], + 'author': 'Antiun Ingeniería S.L., ' + 'Odoo Community Association (OCA)', + 'website': 'http://www.antiun.com', + 'license': 'AGPL-3', + 'installable': True, +} diff --git a/base_location_nuts/i18n/es.po b/base_location_nuts/i18n/es.po new file mode 100644 index 000000000..1bbee771d --- /dev/null +++ b/base_location_nuts/i18n/es.po @@ -0,0 +1,220 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_location_nuts +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-06-11 15:26+0000\n" +"PO-Revision-Date: 2015-06-11 15:26+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_location_nuts +#: view:nuts.import:base_location_nuts.nuts_import_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: base_location_nuts +#: field:res.partner.nuts,children:0 +msgid "Children" +msgstr "Hijos" + +#. module: base_location_nuts +#: field:res.partner.nuts,code:0 +msgid "Code" +msgstr "Código" + +#. module: base_location_nuts +#: view:res.partner.nuts:base_location_nuts.view_res_partner_nuts_filter +#: field:res.partner.nuts,country_id:0 +msgid "Country" +msgstr "País" + +#. module: base_location_nuts +#: field:nuts.import,create_uid:0 +#: field:res.partner.nuts,create_uid:0 +msgid "Created by" +msgstr "Creado por" + +#. module: base_location_nuts +#: field:nuts.import,create_date:0 +#: field:res.partner.nuts,create_date:0 +msgid "Created on" +msgstr "Creado en" + +#. module: base_location_nuts +#: code:addons/base_location_nuts/wizard/nuts_import.py:149 +#, python-format +msgid "Downloaded file is not a valid XML file" +msgstr "El fichero descargado no es un fichero XML válido" + +#. module: base_location_nuts +#: code:addons/base_location_nuts/wizard/nuts_import.py:141 +#, python-format +msgid "Got an error %d when trying to download the file %s." +msgstr "Error %d al intentar descargar el fichero %s." + +#. module: base_location_nuts +#: code:addons/base_location_nuts/wizard/nuts_import.py:137 +#, python-format +msgid "Got an error when trying to download the file: %s." +msgstr "Error al intentar descargar el fichero: %s." + +#. module: base_location_nuts +#: view:res.partner.nuts:base_location_nuts.view_res_partner_nuts_filter +msgid "Group By" +msgstr "Agrupar por" + +#. module: base_location_nuts +#: field:nuts.import,id:0 +#: field:res.partner.nuts,id:0 +msgid "ID" +msgstr "ID" + +#. module: base_location_nuts +#: view:nuts.import:base_location_nuts.nuts_import_form +msgid "Import" +msgstr "Importar" + +#. module: base_location_nuts +#: model:ir.ui.menu,name:base_location_nuts.nuts_import_menu +msgid "Import NUTS 2013" +msgstr "Importar NUTS 2013" + +#. module: base_location_nuts +#: model:ir.actions.act_window,name:base_location_nuts.nuts_import_action +#: view:nuts.import:base_location_nuts.nuts_import_form +msgid "Import NUTS 2013 from RAMON" +msgstr "Importar NUTS 2013 desde RAMON" + +#. module: base_location_nuts +#: model:ir.model,name:base_location_nuts.model_nuts_import +msgid "Import NUTS items from European RAMON service" +msgstr "Importar regiones NUTS desde el servicio europeo RAMON" + +#. module: base_location_nuts +#: field:nuts.import,write_uid:0 +#: field:res.partner.nuts,write_uid:0 +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: base_location_nuts +#: field:nuts.import,write_date:0 +#: field:res.partner.nuts,write_date:0 +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: base_location_nuts +#: view:res.partner.nuts:base_location_nuts.view_res_partner_nuts_filter +#: field:res.partner.nuts,level:0 +msgid "Level" +msgstr "Nivel" + +#. module: base_location_nuts +#: model:ir.model,name:base_location_nuts.model_res_partner_nuts +#: view:res.partner.nuts:base_location_nuts.res_partner_nuts_form +msgid "NUTS Item" +msgstr "Región NUTS" + +#. module: base_location_nuts +#: model:ir.actions.act_window,name:base_location_nuts.res_partner_nuts_action +#: model:ir.ui.menu,name:base_location_nuts.res_partner_nuts_menu +#: view:res.partner.nuts:base_location_nuts.res_partner_nuts_tree +msgid "NUTS Items" +msgstr "Regiones NUTS" + +#. module: base_location_nuts +#: field:res.partner.nuts,name:0 +msgid "Name" +msgstr "Nombre" + +#. module: base_location_nuts +#: field:res.partner.nuts,parent_left:0 +msgid "Parent Left" +msgstr "Padre izquierda" + +#. module: base_location_nuts +#: field:res.partner.nuts,parent_right:0 +msgid "Parent Right" +msgstr "Padre derecha" + +#. module: base_location_nuts +#: field:res.partner.nuts,parent_id:0 +msgid "Parent id" +msgstr "ID del padre" + +#. module: base_location_nuts +#: model:ir.model,name:base_location_nuts.model_res_partner +msgid "Partner" +msgstr "Empresa" + +#. module: base_location_nuts +#: code:addons/base_location_nuts/models/res_partner.py:53 +#: view:res.partner:base_location_nuts.view_res_partner_filter_nuts +#: field:res.partner,region:0 +#, python-format +msgid "Region" +msgstr "Región" + +#. module: base_location_nuts +#: view:res.partner:base_location_nuts.view_res_partner_filter_nuts +msgid "Salesperson" +msgstr "Comercial" + +#. module: base_location_nuts +#: view:res.partner.nuts:base_location_nuts.view_res_partner_nuts_filter +msgid "Search NUTS" +msgstr "Buscar NUTS" + +#. module: base_location_nuts +#: field:res.partner.nuts,state_id:0 +msgid "State" +msgstr "Provincia" + +#. module: base_location_nuts +#: code:addons/base_location_nuts/models/res_partner.py:54 +#: view:res.partner:base_location_nuts.view_res_partner_filter_nuts +#: field:res.partner,substate:0 +#, python-format +msgid "Substate" +msgstr "Estado" + +#. module: base_location_nuts +#: view:nuts.import:base_location_nuts.nuts_import_form +msgid "This wizard will download the lastest version of\n" +" NUTS 2013 from Europe RAMON metadata service.\n" +" Updating or creating new NUTS entries if not\n" +" found already in the system, and DELETING MISSING\n" +" ENTRIES from new downloaded file." +msgstr "Este asistente descargará la última version de\n" +" NUTS 2013 desde el servicio de matadatos europeo RAMON.\n" +" Actualizando o creando nuevas regiones NUTS si no\n" +" las encuentra en el sistemma, y BORRANDO LAS QUE NO ENCUENTRE\n" +" en el nuevo fichero descargado." + +#. module: base_location_nuts +#: code:addons/base_location_nuts/wizard/nuts_import.py:116 +#, python-format +msgid "Value not found for mandatory field %s" +msgstr "El valor no se ha encontrado para el campo obligatorio %s" + +#. module: base_location_nuts +#: model:ir.actions.todo,note:base_location_nuts.config_wizard_nuts +msgid "You can import NUTS from RAMON european service." +msgstr "Usted puede importar NUTS desde el servicion europeo RAMON." + +#. module: base_location_nuts +#: model:ir.actions.act_window,help:base_location_nuts.res_partner_nuts_action +msgid "You must click at import wizard to populate NUTS items\n" +" in Odoo database in:\n" +" Sales > Configuration > Address Book > Localization > Import NUTS 2013" +msgstr "Debes clicar en el asistente de importación para crear las\n" +" las regiones NUTS en la base de datos de Odoo, en el menú:\n" +" Ventas > Configuración > Libreta de direcciones > Localización > Importar NUTS 2013" + diff --git a/base_location_nuts/models/__init__.py b/base_location_nuts/models/__init__.py new file mode 100644 index 000000000..a88f82ad0 --- /dev/null +++ b/base_location_nuts/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in root directory +############################################################################## + +from . import res_partner_nuts +from . import res_partner diff --git a/base_location_nuts/models/res_partner.py b/base_location_nuts/models/res_partner.py new file mode 100644 index 000000000..904580980 --- /dev/null +++ b/base_location_nuts/models/res_partner.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in root directory +############################################################################## + +from openerp import models, fields, api +from openerp.tools.translate import _ +import collections + + +def dict_recursive_update(d, u): + for k, v in u.iteritems(): + if isinstance(v, collections.Mapping): + r = dict_recursive_update(d.get(k, {}), v) + d[k] = r + else: + d[k] = u[k] + return d + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + region = fields.Many2one(comodel_name='res.partner.nuts', + string="Region") + substate = fields.Many2one(comodel_name='res.partner.nuts', + string="Substate") + lbl_region = fields.Char(compute='_labels_get') + lbl_substate = fields.Char(compute='_labels_get') + + @api.one + @api.depends('country_id') + def _labels_get(self): + self.lbl_region = _('Region') + self.lbl_substate = _('Substate') + + @api.multi + def onchange_state(self, state_id): + result = super(ResPartner, self).onchange_state(state_id) + if not state_id: + changes = { + 'domain': { + 'substate': [], + 'region': [], + }, + 'value': { + 'substate': False, + 'region': False, + } + } + dict_recursive_update(result, changes) + return result + + @api.onchange('substate', 'region') + def onchange_substate_or_region(self): + result = {'domain': {}} + if not self.substate: + result['domain']['substate'] = [] + if not self.region: + result['domain']['region'] = [] + return result diff --git a/base_location_nuts/models/res_partner_nuts.py b/base_location_nuts/models/res_partner_nuts.py new file mode 100644 index 000000000..f8e62f20b --- /dev/null +++ b/base_location_nuts/models/res_partner_nuts.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in root directory +############################################################################## + +from openerp import models, fields + + +class ResPartnerNuts(models.Model): + _name = 'res.partner.nuts' + _order = "parent_left" + _parent_order = "name" + _parent_store = True + _description = "NUTS Item" + + # NUTS fields + level = fields.Integer(required=True) + code = fields.Char(required=True) + name = fields.Char(required=True, translate=True) + country_id = fields.Many2one(comodel_name='res.country', string="Country", + required=True) + state_id = fields.Many2one(comodel_name='res.country.state', + string='State') + # Parent hierarchy + parent_id = fields.Many2one(comodel_name='res.partner.nuts', + ondelete='restrict') + children = fields.One2many(comodel_name='res.partner.nuts', + inverse_name='parent_id') + parent_left = fields.Integer('Parent Left', select=True) + parent_right = fields.Integer('Parent Right', select=True) diff --git a/base_location_nuts/security/ir.model.access.csv b/base_location_nuts/security/ir.model.access.csv new file mode 100644 index 000000000..5d38ac660 --- /dev/null +++ b/base_location_nuts/security/ir.model.access.csv @@ -0,0 +1,2 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_res_partner_nuts_user","res_partner_nuts group_user","model_res_partner_nuts","base.group_user",1,0,0,0 diff --git a/base_location_nuts/static/description/icon.png b/base_location_nuts/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b1044a4ba56b0e10e0633636a55e912cd7afcfc8 GIT binary patch literal 7920 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 a3H~2P#)&H@&Sr!F0000 + + + + + NUTS Items tree + res.partner.nuts + + + + + + + + + + + NUTS Items tree + res.partner.nuts + +
+ + + + + + + + + + + + +
+
+
+ + + NUTS Items + ir.actions.act_window + res.partner.nuts + form + tree,form + You must click at import wizard to populate NUTS items + in Odoo database in: + Sales > Configuration > Address Book > Localization > Import NUTS 2013 + + + + NUTS search filters + res.partner.nuts + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/base_location_nuts/views/res_partner_view.xml b/base_location_nuts/views/res_partner_view.xml new file mode 100644 index 000000000..55192e41c --- /dev/null +++ b/base_location_nuts/views/res_partner_view.xml @@ -0,0 +1,78 @@ + + + + + + Partner form with NUTS + res.partner + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + + Partner search with NUTS + res.partner + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/base_location_nuts/wizard/__init__.py b/base_location_nuts/wizard/__init__.py new file mode 100644 index 000000000..1f9d83bcf --- /dev/null +++ b/base_location_nuts/wizard/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in root directory +############################################################################## + +from . import nuts_import diff --git a/base_location_nuts/wizard/nuts_import.py b/base_location_nuts/wizard/nuts_import.py new file mode 100644 index 000000000..e63f908fe --- /dev/null +++ b/base_location_nuts/wizard/nuts_import.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in root directory +############################################################################## + +from openerp import models, api, _ +from openerp.exceptions import Warning +import requests +import re +import logging +from lxml import etree +from collections import OrderedDict + +from pprint import pformat + +logger = logging.getLogger(__name__) + + +class NutsImport(models.TransientModel): + _name = 'nuts.import' + _description = 'Import NUTS items from European RAMON service' + _parents = [False, False, False, False] + _countries = { + "BE": False, + "BG": False, + "CZ": False, + "DK": False, + "DE": False, + "EE": False, + "IE": False, + "GR": False, # EL + "ES": False, + "FR": False, + "HR": False, + "IT": False, + "CY": False, + "LV": False, + "LT": False, + "LU": False, + "HU": False, + "MT": False, + "NL": False, + "AT": False, + "PL": False, + "PT": False, + "RO": False, + "SI": False, + "SK": False, + "FI": False, + "SE": False, + "GB": False, # UK + } + _current_country = False + _map = OrderedDict([ + ('level', { + 'xpath': '', 'attrib': 'idLevel', + 'type': 'integer', 'required': True}), + ('code', { + 'xpath': './Label/LabelText[@language="ALL"]', + 'type': 'string', 'required': True}), + ('name', { + 'xpath': './Label/LabelText[@language="EN"]', + 'type': 'string', 'required': True}), + ]) + + def _check_node(self, node): + if node.get('id') and node.get('idLevel'): + return True + return False + + def _mapping(self, node): + item = {} + for k, v in self._map.iteritems(): + field_xpath = v.get('xpath', '') + field_attrib = v.get('attrib', False) + field_type = v.get('type', 'string') + field_required = v.get('required', False) + value = '' + if field_xpath: + n = node.find(field_xpath) + else: + n = node + if n is not None: + if field_attrib: + value = n.get(field_attrib, '') + else: + value = n.text + if field_type == 'integer': + try: + value = int(value) + except: + value = 0 + else: + logger.debug("xpath = '%s', not found" % field_xpath) + if field_required and not value: + raise Warning( + _('Value not found for mandatory field %s' % k)) + item[k] = value + return item + + def _download_nuts(self): + url_base = 'http://ec.europa.eu' + url_path = '/eurostat/ramon/nomenclatures/index.cfm' + url_params = { + 'TargetUrl': 'ACT_OTH_CLS_DLD', + 'StrNom': 'NUTS_2013', + 'StrFormat': 'XML', + 'StrLanguageCode': 'EN', + 'StrLayoutCode': 'HIERARCHIC' + } + url = url_base + url_path + '?' + url += '&'.join([k + '=' + v for k, v in url_params.iteritems()]) + logger.info('Starting to download %s' % url) + try: + res_request = requests.get(url) + except Exception, e: + raise Warning( + _('Got an error when trying to download the file: %s.') % + str(e)) + if res_request.status_code != requests.codes.ok: + raise Warning( + _('Got an error %d when trying to download the file %s.') + % (res_request.status_code, url)) + logger.info('Download successfully %d bytes' % + len(res_request.content)) + # Workaround XML: Remove all characters before GR (Greece) + # UK => GB (United Kingdom) + self._countries['EL'] = self._countries['GR'] + self._countries['UK'] = self._countries['GB'] + logger.info('_load_countries = %s' % pformat(self._countries)) + + @api.model + def state_mapping(self, data, node): + # Method to inherit and add state_id relation depending on country + level = data.get('level', 0) + code = data.get('code', '') + if level == 1: + self._current_country = self._countries[code] + return { + 'country_id': self._current_country.id, + } + + @api.model + def create_or_update_nuts(self, node): + if not self._check_node(node): + return False + + nuts_model = self.env['res.partner.nuts'] + data = self._mapping(node) + data.update(self.state_mapping(data, node)) + level = data.get('level', 0) + if level >= 2 and level <= 5: + data['parent_id'] = self._parents[level - 2] + nuts = nuts_model.search([('level', '=', data['level']), + ('code', '=', data['code'])]) + if nuts: + nuts.write(data) + else: + nuts = nuts_model.create(data) + if level >= 1 and level <= 4: + self._parents[level - 1] = nuts.id + return nuts + + @api.one + def run_import(self): + nuts_model = self.env['res.partner.nuts'].\ + with_context(defer_parent_store_computation=True) + self._load_countries() + # All current NUTS (for available countries), + # delete if not found above + nuts_to_delete = nuts_model.search( + [('country_id', 'in', [x.id for x in self._countries.values()])]) + # Download NUTS in english, create or update + logger.info('Import NUTS 2013 English') + xmlcontent = self._download_nuts() + dom = etree.fromstring(xmlcontent) + for node in dom.iter('Item'): + logger.info('Reading level=%s, id=%s' % + (node.get('idLevel', 'N/A'), + node.get('id', 'N/A'))) + nuts = self.create_or_update_nuts(node) + if nuts and nuts in nuts_to_delete: + nuts_to_delete -= nuts + # Delete obsolete NUTS + if nuts_to_delete: + logger.info('%d NUTS entries deleted' % len(nuts_to_delete)) + nuts_to_delete.unlink() + logger.info( + 'The wizard to create NUTS entries from RAMON ' + 'has been successfully completed.') + + return True diff --git a/base_location_nuts/wizard/nuts_import_view.xml b/base_location_nuts/wizard/nuts_import_view.xml new file mode 100644 index 000000000..6acd7bdbc --- /dev/null +++ b/base_location_nuts/wizard/nuts_import_view.xml @@ -0,0 +1,51 @@ + + + + + + NUTS import + nuts.import + +
+
+ This wizard will download the lastest version of + NUTS 2013 from Europe RAMON metadata service. + Updating or creating new NUTS entries if not + found already in the system, and DELETING MISSING + ENTRIES from new downloaded file. +
+
+
+
+
+
+ + + Import NUTS 2013 from RAMON + ir.actions.act_window + nuts.import + + form + form + new + + + + + + Import NUTS 2013 from RAMON + You can import NUTS from RAMON european service. + + 20 + automatic + + +
+