Browse Source

Pre-commit

14.0
Andrea 4 years ago
committed by Andrii Skrypka
parent
commit
21dabc5265
  1. 36
      base_location_nuts/__manifest__.py
  2. 2
      base_location_nuts/migrations/12.0.1.0.0/post-migration.py
  3. 3
      base_location_nuts/models/res_country.py
  4. 83
      base_location_nuts/models/res_partner.py
  5. 28
      base_location_nuts/models/res_partner_nuts.py
  6. 69
      base_location_nuts/tests/test_base_location_nuts.py
  7. 30
      base_location_nuts/views/res_country_view.xml
  8. 134
      base_location_nuts/views/res_partner_nuts_view.xml
  9. 99
      base_location_nuts/views/res_partner_view.xml
  10. 172
      base_location_nuts/wizard/nuts_import.py
  11. 79
      base_location_nuts/wizard/nuts_import_view.xml

36
base_location_nuts/__manifest__.py

@ -4,26 +4,20 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': 'NUTS Regions',
'category': 'Localisation/Europe',
'version': '12.0.1.0.0',
'depends': [
'contacts',
"name": "NUTS Regions",
"category": "Localisation/Europe",
"version": "12.0.1.0.0",
"depends": ["contacts",],
"data": [
"views/res_country_view.xml",
"views/res_partner_nuts_view.xml",
"views/res_partner_view.xml",
"wizard/nuts_import_view.xml",
"security/ir.model.access.csv",
], ],
'data': [
'views/res_country_view.xml',
'views/res_partner_nuts_view.xml',
'views/res_partner_view.xml',
'wizard/nuts_import_view.xml',
'security/ir.model.access.csv',
],
'images': [
'images/new_fields.png',
],
'author': 'Tecnativa, '
'Agile Business Group, '
'Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/partner-contact/',
'license': 'AGPL-3',
'installable': True,
"images": ["images/new_fields.png",],
"author": "Tecnativa, " "Agile Business Group, " "Odoo Community Association (OCA)",
"website": "https://github.com/OCA/partner-contact/",
"license": "AGPL-3",
"installable": True,
} }

2
base_location_nuts/migrations/12.0.1.0.0/post-migration.py

@ -3,4 +3,4 @@ from openupgradelib import openupgrade
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
env['res.partner.nuts']._parent_store_compute()
env["res.partner.nuts"]._parent_store_compute()

3
base_location_nuts/models/res_country.py

@ -8,5 +8,4 @@ from odoo import fields, models
class ResCountry(models.Model): class ResCountry(models.Model):
_inherit = "res.country" _inherit = "res.country"
state_level = fields.Integer(
help="Level for the state NUTS category.")
state_level = fields.Integer(help="Level for the state NUTS category.")

83
base_location_nuts/models/res_partner.py

@ -6,20 +6,26 @@ from odoo import api, fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
nuts1_id = fields.Many2one( nuts1_id = fields.Many2one(
comodel_name='res.partner.nuts', domain=[('level', '=', 1)],
string="NUTS L1")
comodel_name="res.partner.nuts", domain=[("level", "=", 1)], string="NUTS L1"
)
nuts2_id = fields.Many2one( nuts2_id = fields.Many2one(
comodel_name='res.partner.nuts', domain=[('level', '=', 2)],
string="NUTS L2", oldname="region")
comodel_name="res.partner.nuts",
domain=[("level", "=", 2)],
string="NUTS L2",
oldname="region",
)
nuts3_id = fields.Many2one( nuts3_id = fields.Many2one(
comodel_name='res.partner.nuts', domain=[('level', '=', 3)],
string="NUTS L3", oldname="substate")
comodel_name="res.partner.nuts",
domain=[("level", "=", 3)],
string="NUTS L3",
oldname="substate",
)
nuts4_id = fields.Many2one( nuts4_id = fields.Many2one(
comodel_name='res.partner.nuts', domain=[('level', '=', 4)],
string="NUTS L4")
comodel_name="res.partner.nuts", domain=[("level", "=", 4)], string="NUTS L4"
)
def _onchange_nuts(self, level): def _onchange_nuts(self, level):
field = self["nuts%d_id" % level] field = self["nuts%d_id" % level]
@ -32,82 +38,85 @@ class ResPartner(models.Model):
if level > 1: if level > 1:
parent_id = field.parent_id parent_id = field.parent_id
if parent_id: if parent_id:
nuts_parent_level = 'nuts%d_id' % (level - 1)
nuts_parent_level = "nuts%d_id" % (level - 1)
parent_field = self[nuts_parent_level] parent_field = self[nuts_parent_level]
if parent_field != parent_id: if parent_field != parent_id:
self[nuts_parent_level] = parent_id self[nuts_parent_level] = parent_id
result = {} result = {}
if country_id and level < 4: if country_id and level < 4:
result['domain'] = {}
result["domain"] = {}
while level < 4: while level < 4:
parent_field = 'nuts%d_id' % level
domain_field = 'nuts%d_id' % (level + 1)
parent_field = "nuts%d_id" % level
domain_field = "nuts%d_id" % (level + 1)
parent_id = self[parent_field].id parent_id = self[parent_field].id
if parent_id: if parent_id:
result['domain'][domain_field] = [
('parent_id', '=', parent_id),
result["domain"][domain_field] = [
("parent_id", "=", parent_id),
] ]
level += 1 level += 1
return result return result
@api.onchange('nuts4_id')
@api.onchange("nuts4_id")
def _onchange_nuts4_id(self): def _onchange_nuts4_id(self):
return self._onchange_nuts(4) return self._onchange_nuts(4)
@api.onchange('nuts3_id')
@api.onchange("nuts3_id")
def _onchange_nuts3_id(self): def _onchange_nuts3_id(self):
return self._onchange_nuts(3) return self._onchange_nuts(3)
@api.onchange('nuts2_id')
@api.onchange("nuts2_id")
def _onchange_nuts2_id(self): def _onchange_nuts2_id(self):
return self._onchange_nuts(2) return self._onchange_nuts(2)
@api.onchange('nuts1_id')
@api.onchange("nuts1_id")
def _onchange_nuts1_id(self): def _onchange_nuts1_id(self):
return self._onchange_nuts(1) return self._onchange_nuts(1)
@api.onchange('country_id')
@api.onchange("country_id")
def _onchange_country_id_base_location_nuts(self): def _onchange_country_id_base_location_nuts(self):
"""Sensible values and domains for related fields.""" """Sensible values and domains for related fields."""
fields = ['state_id', 'nuts1_id', 'nuts2_id', 'nuts3_id', 'nuts4_id']
country_domain = ([('country_id', '=', self.country_id.id)]
if self.country_id else [])
fields = ["state_id", "nuts1_id", "nuts2_id", "nuts3_id", "nuts4_id"]
country_domain = (
[("country_id", "=", self.country_id.id)] if self.country_id else []
)
domain = dict() domain = dict()
for field in fields: for field in fields:
if self.country_id and self[field].country_id != self.country_id: if self.country_id and self[field].country_id != self.country_id:
self[field] = False self[field] = False
domain[field] = list(country_domain) # Using list() to copy domain[field] = list(country_domain) # Using list() to copy
fields.remove('state_id')
fields.remove("state_id")
for field in fields: for field in fields:
level = int(field[4]) level = int(field[4])
domain[field].append(('level', '=', level))
domain[field].append(("level", "=", level))
if self.country_id: if self.country_id:
nuts1 = self.env['res.partner.nuts'].search([
('level', '=', 1),
('country_id', '=', self.country_id.id),
], limit=1)
nuts1 = self.env["res.partner.nuts"].search(
[("level", "=", 1), ("country_id", "=", self.country_id.id),], limit=1
)
if self.nuts1_id.id != nuts1.id: if self.nuts1_id.id != nuts1.id:
self.nuts1_id = nuts1.id self.nuts1_id = nuts1.id
return { return {
'domain': domain,
"domain": domain,
} }
@api.onchange('state_id')
@api.onchange("state_id")
def onchange_state_id_base_location_nuts(self): def onchange_state_id_base_location_nuts(self):
if self.state_id: if self.state_id:
self.country_id = self.state_id.country_id self.country_id = self.state_id.country_id
if self.state_id.country_id.state_level: if self.state_id.country_id.state_level:
nuts_state = self.env['res.partner.nuts'].search([
('level', '=', self.state_id.country_id.state_level),
('state_id', '=', self.state_id.id)
], limit=1)
nuts_state = self.env["res.partner.nuts"].search(
[
("level", "=", self.state_id.country_id.state_level),
("state_id", "=", self.state_id.id),
],
limit=1,
)
if nuts_state: if nuts_state:
field = 'nuts%d_id' % self.state_id.country_id.state_level
field = "nuts%d_id" % self.state_id.country_id.state_level
self[field] = nuts_state self[field] = nuts_state
@api.model @api.model
def _address_fields(self): def _address_fields(self):
fields = super(ResPartner, self)._address_fields() fields = super(ResPartner, self)._address_fields()
if fields: if fields:
fields += ['nuts1_id', 'nuts2_id', 'nuts3_id', 'nuts4_id']
fields += ["nuts1_id", "nuts2_id", "nuts3_id", "nuts4_id"]
return fields return fields

28
base_location_nuts/models/res_partner_nuts.py

@ -6,25 +6,27 @@ from odoo import fields, models
class ResPartnerNuts(models.Model): class ResPartnerNuts(models.Model):
_name = 'res.partner.nuts'
_order = 'parent_path'
_parent_order = 'name'
_name = "res.partner.nuts"
_order = "parent_path"
_parent_order = "name"
_parent_store = True _parent_store = True
_description = 'NUTS Item'
_description = "NUTS Item"
# NUTS fields # NUTS fields
level = fields.Integer(required=True) level = fields.Integer(required=True)
code = fields.Char(required=True) code = fields.Char(required=True)
name = fields.Char(required=True, translate=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')
country_id = fields.Many2one(
comodel_name="res.country", string="Country", required=True
)
state_id = fields.Many2one(comodel_name="res.country.state", string="State")
not_updatable = fields.Boolean() not_updatable = fields.Boolean()
# Parent hierarchy # Parent hierarchy
parent_id = fields.Many2one(comodel_name='res.partner.nuts',
ondelete='restrict')
parent_id = fields.Many2one(comodel_name="res.partner.nuts", ondelete="restrict")
parent_path = fields.Char(index=True) parent_path = fields.Char(index=True)
child_ids = fields.One2many(comodel_name='res.partner.nuts',
inverse_name='parent_id', string='Children',
oldname='children')
child_ids = fields.One2many(
comodel_name="res.partner.nuts",
inverse_name="parent_id",
string="Children",
oldname="children",
)

69
base_location_nuts/tests/test_base_location_nuts.py

@ -1,39 +1,34 @@
# Copyright 2017 David Vidal <david.vidal@tecnativa.com> # Copyright 2017 David Vidal <david.vidal@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests import common
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tests import common
class TestBaseLocationNuts(common.SavepointCase): class TestBaseLocationNuts(common.SavepointCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TestBaseLocationNuts, cls).setUpClass() super(TestBaseLocationNuts, cls).setUpClass()
cls.importer = cls.env['nuts.import']
cls.importer = cls.env["nuts.import"]
cls.importer.run_import() # loads nuts cls.importer.run_import() # loads nuts
cls.country_1 = cls.env['res.country'].search([('code', '=', 'ES')])
cls.country_2 = cls.env['res.country'].search([('code', '=', 'PT')])
cls.nuts_model = cls.env['res.partner.nuts']
cls.nuts1_2 = cls.nuts_model.search([('code', '=', 'PT')])
cls.nuts2_1 = cls.nuts_model.search([('code', '=', 'ES2')])
cls.nuts3_1 = cls.nuts_model.search([('code', '=', 'ES24')])
cls.nuts4_1 = cls.nuts_model.search([('code', '=', 'ES243')])
cls.nuts4_2 = cls.nuts_model.search([('code', '=', 'ES300')])
cls.partner = cls.env['res.partner'].create({
'name': 'Test partner',
'country_id': cls.country_1.id,
})
cls.state_1 = cls.env['res.country.state'].create({
'name': 'Zaragoza Test',
'code': 'ZT',
'country_id': cls.country_1.id,
})
cls.country_1 = cls.env["res.country"].search([("code", "=", "ES")])
cls.country_2 = cls.env["res.country"].search([("code", "=", "PT")])
cls.nuts_model = cls.env["res.partner.nuts"]
cls.nuts1_2 = cls.nuts_model.search([("code", "=", "PT")])
cls.nuts2_1 = cls.nuts_model.search([("code", "=", "ES2")])
cls.nuts3_1 = cls.nuts_model.search([("code", "=", "ES24")])
cls.nuts4_1 = cls.nuts_model.search([("code", "=", "ES243")])
cls.nuts4_2 = cls.nuts_model.search([("code", "=", "ES300")])
cls.partner = cls.env["res.partner"].create(
{"name": "Test partner", "country_id": cls.country_1.id,}
)
cls.state_1 = cls.env["res.country.state"].create(
{"name": "Zaragoza Test", "code": "ZT", "country_id": cls.country_1.id,}
)
cls.nuts4_1.state_id = cls.state_1 cls.nuts4_1.state_id = cls.state_1
cls.state_2 = cls.env['res.country.state'].create({
'name': 'Madrid Test',
'code': 'MT',
'country_id': cls.country_1.id,
})
cls.state_2 = cls.env["res.country.state"].create(
{"name": "Madrid Test", "code": "MT", "country_id": cls.country_1.id,}
)
cls.nuts4_2.state_id = cls.state_2 cls.nuts4_2.state_id = cls.state_2
cls.country_1.state_level = 4 cls.country_1.state_level = 4
@ -45,12 +40,10 @@ class TestBaseLocationNuts(common.SavepointCase):
def test_onchange_nuts(self): def test_onchange_nuts(self):
self.partner.country_id = self.country_2 self.partner.country_id = self.country_2
self.partner._onchange_country_id_base_location_nuts() self.partner._onchange_country_id_base_location_nuts()
self.assertEqual(self.partner.nuts1_id.country_id,
self.partner.country_id)
self.assertEqual(self.partner.nuts1_id.country_id, self.partner.country_id)
self.partner.nuts4_id = self.nuts4_1 self.partner.nuts4_id = self.nuts4_1
self.partner._onchange_nuts4_id() self.partner._onchange_nuts4_id()
self.assertEqual(self.partner.country_id,
self.country_1)
self.assertEqual(self.partner.country_id, self.country_1)
self.assertEqual(self.partner.nuts3_id, self.nuts3_1) self.assertEqual(self.partner.nuts3_id, self.nuts3_1)
self.partner._onchange_nuts3_id() self.partner._onchange_nuts3_id()
self.assertEqual(self.partner.nuts2_id, self.nuts2_1) self.assertEqual(self.partner.nuts2_id, self.nuts2_1)
@ -80,18 +73,20 @@ class TestBaseLocationNuts(common.SavepointCase):
def test_download_exceptions(self): def test_download_exceptions(self):
""" Tests download exceptions """ """ Tests download exceptions """
with self.assertRaises(UserError): with self.assertRaises(UserError):
self.importer._download_nuts(url_base='htttt://test.com')
self.importer._download_nuts(url_base="htttt://test.com")
with self.assertRaises(UserError): with self.assertRaises(UserError):
self.importer._download_nuts(url_base='http://ec.europa.eu/_404')
self.importer._download_nuts(url_base="http://ec.europa.eu/_404")
def create_new_parent(self, orig_parent): def create_new_parent(self, orig_parent):
new_parent = self.nuts_model.create({
'level': orig_parent.level,
'code': 'NEW' + orig_parent.code,
'name': 'New parent',
'country_id': orig_parent.country_id.id,
'not_updatable': False
})
new_parent = self.nuts_model.create(
{
"level": orig_parent.level,
"code": "NEW" + orig_parent.code,
"name": "New parent",
"country_id": orig_parent.country_id.id,
"not_updatable": False,
}
)
return new_parent return new_parent
def test_no_update(self): def test_no_update(self):

30
base_location_nuts/views/res_country_view.xml

@ -1,19 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_country_form" model="ir.ui.view">
<field name="name">NUTS fields</field>
<field name="model">res.country</field>
<field name="inherit_id" ref="base.view_country_form"/>
<field name="arch" type="xml">
<xpath expr="/form">
<group name="nuts" string="NUTS">
<group>
<field name="state_level"/>
<record id="view_country_form" model="ir.ui.view">
<field name="name">NUTS fields</field>
<field name="model">res.country</field>
<field name="inherit_id" ref="base.view_country_form" />
<field name="arch" type="xml">
<xpath expr="/form">
<group name="nuts" string="NUTS">
<group>
<field name="state_level" />
</group>
</group> </group>
</group>
</xpath>
</field>
</record>
</xpath>
</field>
</record>
</odoo> </odoo>

134
base_location_nuts/views/res_partner_nuts_view.xml

@ -1,77 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="res_partner_nuts_tree" model="ir.ui.view">
<field name="name">NUTS Items tree</field>
<field name="model">res.partner.nuts</field>
<field name="arch" type="xml">
<tree string="NUTS Items">
<field name="level"/>
<field name="code"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="res_partner_nuts_form" model="ir.ui.view">
<field name="name">NUTS Items form</field>
<field name="model">res.partner.nuts</field>
<field name="arch" type="xml">
<form string="NUTS Item">
<group>
<record id="res_partner_nuts_tree" model="ir.ui.view">
<field name="name">NUTS Items tree</field>
<field name="model">res.partner.nuts</field>
<field name="arch" type="xml">
<tree string="NUTS Items">
<field name="level" />
<field name="code" />
<field name="name" />
</tree>
</field>
</record>
<record id="res_partner_nuts_form" model="ir.ui.view">
<field name="name">NUTS Items form</field>
<field name="model">res.partner.nuts</field>
<field name="arch" type="xml">
<form string="NUTS Item">
<group> <group>
<field name="level"/>
<field name="code"/>
<field name="parent_id"/>
<field name="name"/>
<field name="not_updatable"/>
<group>
<field name="level" />
<field name="code" />
<field name="parent_id" />
<field name="name" />
<field name="not_updatable" />
</group>
<group>
<field name="country_id" />
<field name="state_id" />
</group>
</group> </group>
<group>
<field name="country_id"/>
<field name="state_id"/>
</group>
</group>
</form>
</field>
</record>
<record id="res_partner_nuts_action" model="ir.actions.act_window">
<field name="name">NUTS Items</field>
<field name="res_model">res.partner.nuts</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help">You must click at import wizard to populate NUTS items
</form>
</field>
</record>
<record id="res_partner_nuts_action" model="ir.actions.act_window">
<field name="name">NUTS Items</field>
<field name="res_model">res.partner.nuts</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help">You must click at import wizard to populate NUTS items
in Odoo database in: in Odoo database in:
Sales > Configuration > Address Book > Localization > Import NUTS 2013</field> Sales > Configuration > Address Book > Localization > Import NUTS 2013</field>
</record>
<record model="ir.ui.view" id="view_res_partner_nuts_filter">
<field name="name">NUTS search filters</field>
<field name="model">res.partner.nuts</field>
<field name="arch" type="xml">
<search string="Search NUTS">
<field name="name"/>
<field name="country_id"/>
<field name="state_id"/>
<group expand="0" string="Group By">
<filter name="country"
</record>
<record model="ir.ui.view" id="view_res_partner_nuts_filter">
<field name="name">NUTS search filters</field>
<field name="model">res.partner.nuts</field>
<field name="arch" type="xml">
<search string="Search NUTS">
<field name="name" />
<field name="country_id" />
<field name="state_id" />
<group expand="0" string="Group By">
<filter
name="country"
string="Country" string="Country"
domain="[]" domain="[]"
context="{'group_by': 'country_id'}"/>
<filter name="level"
context="{'group_by': 'country_id'}"
/>
<filter
name="level"
string="Level" string="Level"
domain="[]" domain="[]"
context="{'group_by': 'level'}"/>
</group>
</search>
</field>
</record>
<menuitem action="res_partner_nuts_action"
id="res_partner_nuts_menu"
groups="base.group_partner_manager"
name="NUTS Items"
parent="contacts.menu_localisation"
sequence="40"/>
context="{'group_by': 'level'}"
/>
</group>
</search>
</field>
</record>
<menuitem
action="res_partner_nuts_action"
id="res_partner_nuts_menu"
groups="base.group_partner_manager"
name="NUTS Items"
parent="contacts.menu_localisation"
sequence="40"
/>
</odoo> </odoo>

99
base_location_nuts/views/res_partner_view.xml

@ -1,57 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record model="ir.ui.view" id="view_partner_form_nuts">
<field name="name">Partner form with NUTS</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet/group//field[@name='country_id']/.." position="after">
<field name="nuts1_id"/>
<field name="nuts2_id"/>
<field name="nuts3_id"/>
<field name="nuts4_id"/>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='country_id']/.." position="after">
<field name="nuts1_id"/>
<field name="nuts2_id"/>
<field name="nuts3_id"/>
<field name="nuts4_id"/>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="view_res_partner_filter_nuts">
<field name="name">Partner search with NUTS</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<field name="category_id" position="after">
<field name="nuts1_id"/>
<field name="nuts2_id"/>
<field name="nuts3_id"/>
<field name="nuts4_id"/>
<record model="ir.ui.view" id="view_partner_form_nuts">
<field name="name">Partner form with NUTS</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//sheet/group//field[@name='country_id']/.." position="after">
<field name="nuts1_id" />
<field name="nuts2_id" />
<field name="nuts3_id" />
<field name="nuts4_id" />
</xpath>
<xpath
expr="//field[@name='child_ids']/form//field[@name='country_id']/.."
position="after"
>
<field name="nuts1_id" />
<field name="nuts2_id" />
<field name="nuts3_id" />
<field name="nuts4_id" />
</xpath>
</field> </field>
<filter name="salesperson" position="after">
<filter name="nuts_l1"
</record>
<record model="ir.ui.view" id="view_res_partner_filter_nuts">
<field name="name">Partner search with NUTS</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter" />
<field name="arch" type="xml">
<field name="category_id" position="after">
<field name="nuts1_id" />
<field name="nuts2_id" />
<field name="nuts3_id" />
<field name="nuts4_id" />
</field>
<filter name="salesperson" position="after">
<filter
name="nuts_l1"
string="NUTS L1" string="NUTS L1"
domain="[]" domain="[]"
context="{'group_by': 'nuts1_id'}"/>
<filter name="nuts_l2"
context="{'group_by': 'nuts1_id'}"
/>
<filter
name="nuts_l2"
string="NUTS L2" string="NUTS L2"
domain="[]" domain="[]"
context="{'group_by': 'nuts2_id'}"/>
<filter name="nuts_l3"
context="{'group_by': 'nuts2_id'}"
/>
<filter
name="nuts_l3"
string="NUTS L3" string="NUTS L3"
domain="[]" domain="[]"
context="{'group_by': 'nuts3_id'}"/>
<filter name="nuts_l4"
context="{'group_by': 'nuts3_id'}"
/>
<filter
name="nuts_l4"
string="NUTS L4" string="NUTS L4"
domain="[]" domain="[]"
context="{'group_by': 'nuts4_id'}"/>
</filter>
</field>
</record>
context="{'group_by': 'nuts4_id'}"
/>
</filter>
</field>
</record>
</odoo> </odoo>

172
base_location_nuts/wizard/nuts_import.py

@ -3,30 +3,33 @@
# Copyright 2017 David Vidal <jairo.llopis@tecnativa.com> # Copyright 2017 David Vidal <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import _, api, models
from odoo.exceptions import UserError
import requests
import re
import logging import logging
from lxml import etree
import re
from collections import OrderedDict from collections import OrderedDict
import requests
from lxml import etree
from odoo import _, api, models
from odoo.exceptions import UserError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Default server values # Default server values
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_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",
}
class NutsImport(models.TransientModel): class NutsImport(models.TransientModel):
_name = 'nuts.import'
_description = 'Import NUTS items from European RAMON service'
_name = "nuts.import"
_description = "Import NUTS items from European RAMON service"
_parents = [False, False, False, False] _parents = [False, False, False, False]
_countries = { _countries = {
"BE": False, "BE": False,
@ -59,53 +62,65 @@ class NutsImport(models.TransientModel):
"GB": False, # UK "GB": False, # UK
} }
_current_country = False _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}),
])
_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): def _check_node(self, node):
if node.get('id') and node.get('idLevel'):
if node.get("id") and node.get("idLevel"):
return True return True
return False return False
def _mapping(self, node): def _mapping(self, node):
item = {} item = {}
for k, v in self._map.items(): for k, v in self._map.items():
field_xpath = v.get('xpath', '')
field_attrib = v.get('attrib', False)
field_type = v.get('type', 'string')
field_required = v.get('required', False)
value = ''
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: if field_xpath:
n = node.find(field_xpath) n = node.find(field_xpath)
else: else:
n = node n = node
if n is not None: if n is not None:
if field_attrib: if field_attrib:
value = n.get(field_attrib, '')
value = n.get(field_attrib, "")
else: else:
value = n.text value = n.text
if field_type == 'integer':
if field_type == "integer":
try: try:
value = int(value) value = int(value)
except (ValueError, TypeError): except (ValueError, TypeError):
logger.warn( logger.warn(
"Value %s for field %s replaced by 0" %
(value, k))
"Value {} for field {} replaced by 0".format(value, k)
)
value = 0 value = 0
else: else:
logger.debug("xpath = '%s', not found" % field_xpath) logger.debug("xpath = '%s', not found" % field_xpath)
if field_required and not value: if field_required and not value:
raise UserError(
_('Value not found for mandatory field %s' % k))
raise UserError(_("Value not found for mandatory field %s" % k))
item[k] = value item[k] = value
return item return item
@ -116,48 +131,47 @@ class NutsImport(models.TransientModel):
url_path = URL_PATH url_path = URL_PATH
if not url_params: if not url_params:
url_params = URL_PARAMS url_params = URL_PARAMS
url = url_base + url_path + '?'
url += '&'.join([k + '=' + v for k, v in url_params.items()])
logger.info('Starting to download %s' % url)
url = url_base + url_path + "?"
url += "&".join([k + "=" + v for k, v in url_params.items()])
logger.info("Starting to download %s" % url)
try: try:
res_request = requests.get(url) res_request = requests.get(url)
except Exception as e: except Exception as e:
raise UserError( raise UserError(
_('Got an error when trying to download the file: %s.') %
str(e))
_("Got an error when trying to download the file: %s.") % str(e)
)
if res_request.status_code != requests.codes.ok: if res_request.status_code != requests.codes.ok:
raise UserError( raise UserError(
_('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))
_("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 <?xml # Workaround XML: Remove all characters before <?xml
pattern = re.compile(rb'^.*<\?xml', re.DOTALL)
content_fixed = re.sub(pattern, b'<?xml', res_request.content)
if not re.match(rb'<\?xml', content_fixed):
raise UserError(_('Downloaded file is not a valid XML file'))
pattern = re.compile(rb"^.*<\?xml", re.DOTALL)
content_fixed = re.sub(pattern, b"<?xml", res_request.content)
if not re.match(rb"<\?xml", content_fixed):
raise UserError(_("Downloaded file is not a valid XML file"))
return content_fixed return content_fixed
@api.model @api.model
def _load_countries(self): def _load_countries(self):
for k in self._countries: for k in self._countries:
self._countries[k] = self.env['res.country'].search(
[('code', '=', k)])
self._countries[k] = self.env["res.country"].search([("code", "=", k)])
# Workaround to translate some country codes: # Workaround to translate some country codes:
# EL => GR (Greece) # EL => GR (Greece)
# UK => GB (United Kingdom) # UK => GB (United Kingdom)
self._countries['EL'] = self._countries['GR']
self._countries['UK'] = self._countries['GB']
self._countries["EL"] = self._countries["GR"]
self._countries["UK"] = self._countries["GB"]
@api.model @api.model
def state_mapping(self, data, node): def state_mapping(self, data, node):
# Method to inherit and add state_id relation depending on country # Method to inherit and add state_id relation depending on country
level = data.get('level', 0)
code = data.get('code', '')
level = data.get("level", 0)
code = data.get("code", "")
if level == 1: if level == 1:
self._current_country = self._countries[code] self._current_country = self._countries[code]
return { return {
'country_id': self._current_country.id,
"country_id": self._current_country.id,
} }
@api.model @api.model
@ -165,14 +179,15 @@ class NutsImport(models.TransientModel):
if not self._check_node(node): if not self._check_node(node):
return False return False
nuts_model = self.env['res.partner.nuts']
nuts_model = self.env["res.partner.nuts"]
data = self._mapping(node) data = self._mapping(node)
data.update(self.state_mapping(data, node)) data.update(self.state_mapping(data, node))
level = data.get('level', 0)
level = data.get("level", 0)
if level >= 2 and level <= 5: if level >= 2 and level <= 5:
data['parent_id'] = self._parents[level - 2]
nuts = nuts_model.search([('level', '=', data['level']),
('code', '=', data['code'])])
data["parent_id"] = self._parents[level - 2]
nuts = nuts_model.search(
[("level", "=", data["level"]), ("code", "=", data["code"])]
)
if nuts: if nuts:
nuts.filtered(lambda n: not n.not_updatable).write(data) nuts.filtered(lambda n: not n.not_updatable).write(data)
else: else:
@ -183,30 +198,37 @@ class NutsImport(models.TransientModel):
@api.multi @api.multi
def run_import(self): def run_import(self):
nuts_model = self.env['res.partner.nuts'].\
with_context(defer_parent_store_computation=True)
nuts_model = self.env["res.partner.nuts"].with_context(
defer_parent_store_computation=True
)
self._load_countries() self._load_countries()
# All current NUTS (for available countries), # All current NUTS (for available countries),
# delete if not found above # delete if not found above
nuts_to_delete = nuts_model.search( nuts_to_delete = nuts_model.search(
[('country_id', 'in', [x.id for x in self._countries.values()]),
('not_updatable', '=', False)])
[
("country_id", "in", [x.id for x in self._countries.values()]),
("not_updatable", "=", False),
]
)
# Download NUTS in english, create or update # Download NUTS in english, create or update
logger.info('Importing NUTS 2013 English...')
logger.info("Importing NUTS 2013 English...")
xmlcontent = self._download_nuts() xmlcontent = self._download_nuts()
dom = etree.fromstring(xmlcontent) dom = etree.fromstring(xmlcontent)
for node in dom.iter('Item'):
logger.debug('Reading level=%s, id=%s',
node.get('idLevel', 'N/A'),
node.get('id', 'N/A'))
for node in dom.iter("Item"):
logger.debug(
"Reading level=%s, id=%s",
node.get("idLevel", "N/A"),
node.get("id", "N/A"),
)
nuts = self.create_or_update_nuts(node) nuts = self.create_or_update_nuts(node)
if nuts and nuts in nuts_to_delete: if nuts and nuts in nuts_to_delete:
nuts_to_delete -= nuts nuts_to_delete -= nuts
# Delete obsolete NUTS # Delete obsolete NUTS
if nuts_to_delete: if nuts_to_delete:
logger.info('%d NUTS entries deleted' % len(nuts_to_delete))
logger.info("%d NUTS entries deleted" % len(nuts_to_delete))
nuts_to_delete.unlink() nuts_to_delete.unlink()
logger.info( logger.info(
'The wizard to create NUTS entries from RAMON '
'has been successfully completed.')
"The wizard to create NUTS entries from RAMON "
"has been successfully completed."
)
return True return True

79
base_location_nuts/wizard/nuts_import_view.xml

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="nuts_import_form" model="ir.ui.view">
<field name="name">NUTS import</field>
<field name="model">nuts.import</field>
<field name="arch" type="xml">
<form string="Import NUTS 2013 from RAMON">
<div>
<record id="nuts_import_form" model="ir.ui.view">
<field name="name">NUTS import</field>
<field name="model">nuts.import</field>
<field name="arch" type="xml">
<form string="Import NUTS 2013 from RAMON">
<div>
This wizard will download the lastest version of This wizard will download the lastest version of
NUTS 2013 from Europe RAMON metadata service. NUTS 2013 from Europe RAMON metadata service.
Updating or creating new NUTS entries if not Updating or creating new NUTS entries if not
@ -15,35 +14,37 @@
Update or deletion is prevented for NUTS entries with the flag 'Not updatable'. Update or deletion is prevented for NUTS entries with the flag 'Not updatable'.
</div> </div>
<footer>
<button name="run_import" type="object"
class="oe_highlight" string="Import"/>
<button special="cancel" string="Cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<record id="nuts_import_action" model="ir.actions.act_window">
<field name="name">Import NUTS 2013 from RAMON</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">nuts.import</field>
<field name="view_id" ref="nuts_import_form"/>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem action="nuts_import_action"
id="nuts_import_menu"
name="Import NUTS 2013"
parent="contacts.menu_localisation"
sequence="45"/>
<record id="config_wizard_nuts" model="ir.actions.todo">
<field name="name">Import NUTS 2013 from RAMON</field>
<field name="action_id" ref="nuts_import_action"/>
<field name="sequence">20</field>
</record>
<footer>
<button
name="run_import"
type="object"
class="oe_highlight"
string="Import"
/>
<button special="cancel" string="Cancel" class="oe_link" />
</footer>
</form>
</field>
</record>
<record id="nuts_import_action" model="ir.actions.act_window">
<field name="name">Import NUTS 2013 from RAMON</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">nuts.import</field>
<field name="view_id" ref="nuts_import_form" />
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem
action="nuts_import_action"
id="nuts_import_menu"
name="Import NUTS 2013"
parent="contacts.menu_localisation"
sequence="45"
/>
<record id="config_wizard_nuts" model="ir.actions.todo">
<field name="name">Import NUTS 2013 from RAMON</field>
<field name="action_id" ref="nuts_import_action" />
<field name="sequence">20</field>
</record>
</odoo> </odoo>
Loading…
Cancel
Save