You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
7.1 KiB

9 years ago
9 years ago
9 years ago
9 years ago
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 Antonio Espinosa <antonio.espinosa@tecnativa.com>
  3. # Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
  4. # Copyright 2017 David Vidal <jairo.llopis@tecnativa.com>
  5. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
  6. from odoo import _, api, models
  7. from odoo.exceptions import UserError
  8. import requests
  9. import re
  10. import logging
  11. from lxml import etree
  12. from collections import OrderedDict
  13. logger = logging.getLogger(__name__)
  14. # Default server values
  15. URL_BASE = 'http://ec.europa.eu'
  16. URL_PATH = '/eurostat/ramon/nomenclatures/index.cfm'
  17. URL_PARAMS = {'TargetUrl': 'ACT_OTH_CLS_DLD',
  18. 'StrNom': 'NUTS_2013',
  19. 'StrFormat': 'XML',
  20. 'StrLanguageCode': 'EN',
  21. 'StrLayoutCode': 'HIERARCHIC'
  22. }
  23. class NutsImport(models.TransientModel):
  24. _name = 'nuts.import'
  25. _description = 'Import NUTS items from European RAMON service'
  26. _parents = [False, False, False, False]
  27. _countries = {
  28. "BE": False,
  29. "BG": False,
  30. "CZ": False,
  31. "DK": False,
  32. "DE": False,
  33. "EE": False,
  34. "IE": False,
  35. "GR": False, # EL
  36. "ES": False,
  37. "FR": False,
  38. "HR": False,
  39. "IT": False,
  40. "CY": False,
  41. "LV": False,
  42. "LT": False,
  43. "LU": False,
  44. "HU": False,
  45. "MT": False,
  46. "NL": False,
  47. "AT": False,
  48. "PL": False,
  49. "PT": False,
  50. "RO": False,
  51. "SI": False,
  52. "SK": False,
  53. "FI": False,
  54. "SE": False,
  55. "GB": False, # UK
  56. }
  57. _current_country = False
  58. _map = OrderedDict([
  59. ('level', {
  60. 'xpath': '', 'attrib': 'idLevel',
  61. 'type': 'integer', 'required': True}),
  62. ('code', {
  63. 'xpath': './Label/LabelText[@language="ALL"]',
  64. 'type': 'string', 'required': True}),
  65. ('name', {
  66. 'xpath': './Label/LabelText[@language="EN"]',
  67. 'type': 'string', 'required': True}),
  68. ])
  69. def _check_node(self, node):
  70. if node.get('id') and node.get('idLevel'):
  71. return True
  72. return False
  73. def _mapping(self, node):
  74. item = {}
  75. for k, v in self._map.iteritems():
  76. field_xpath = v.get('xpath', '')
  77. field_attrib = v.get('attrib', False)
  78. field_type = v.get('type', 'string')
  79. field_required = v.get('required', False)
  80. value = ''
  81. if field_xpath:
  82. n = node.find(field_xpath)
  83. else:
  84. n = node
  85. if n is not None:
  86. if field_attrib:
  87. value = n.get(field_attrib, '')
  88. else:
  89. value = n.text
  90. if field_type == 'integer':
  91. try:
  92. value = int(value)
  93. except:
  94. value = 0
  95. else:
  96. logger.debug("xpath = '%s', not found" % field_xpath)
  97. if field_required and not value:
  98. raise UserError(
  99. _('Value not found for mandatory field %s' % k))
  100. item[k] = value
  101. return item
  102. def _download_nuts(self, url_base=None, url_path=None, url_params=None):
  103. if not url_base:
  104. url_base = URL_BASE
  105. if not url_path:
  106. url_path = URL_PATH
  107. if not url_params:
  108. url_params = URL_PARAMS
  109. url = url_base + url_path + '?'
  110. url += '&'.join([k + '=' + v for k, v in url_params.iteritems()])
  111. logger.info('Starting to download %s' % url)
  112. try:
  113. res_request = requests.get(url)
  114. except Exception, e:
  115. raise UserError(
  116. _('Got an error when trying to download the file: %s.') %
  117. str(e))
  118. if res_request.status_code != requests.codes.ok:
  119. raise UserError(
  120. _('Got an error %d when trying to download the file %s.')
  121. % (res_request.status_code, url))
  122. logger.info('Download successfully %d bytes' %
  123. len(res_request.content))
  124. # Workaround XML: Remove all characters before <?xml
  125. pattern = re.compile(r'^.*<\?xml', re.DOTALL)
  126. content_fixed = re.sub(pattern, '<?xml', res_request.content)
  127. if not re.match(r'<\?xml', content_fixed):
  128. raise UserError(_('Downloaded file is not a valid XML file'))
  129. return content_fixed
  130. @api.model
  131. def _load_countries(self):
  132. for k in self._countries.keys():
  133. self._countries[k] = self.env['res.country'].search(
  134. [('code', '=', k)])
  135. # Workaround to translate some country codes:
  136. # EL => GR (Greece)
  137. # UK => GB (United Kingdom)
  138. self._countries['EL'] = self._countries['GR']
  139. self._countries['UK'] = self._countries['GB']
  140. @api.model
  141. def state_mapping(self, data, node):
  142. # Method to inherit and add state_id relation depending on country
  143. level = data.get('level', 0)
  144. code = data.get('code', '')
  145. if level == 1:
  146. self._current_country = self._countries[code]
  147. return {
  148. 'country_id': self._current_country.id,
  149. }
  150. @api.model
  151. def create_or_update_nuts(self, node):
  152. if not self._check_node(node):
  153. return False
  154. nuts_model = self.env['res.partner.nuts']
  155. data = self._mapping(node)
  156. data.update(self.state_mapping(data, node))
  157. level = data.get('level', 0)
  158. if level >= 2 and level <= 5:
  159. data['parent_id'] = self._parents[level - 2]
  160. nuts = nuts_model.search([('level', '=', data['level']),
  161. ('code', '=', data['code'])])
  162. if nuts:
  163. nuts.write(data)
  164. else:
  165. nuts = nuts_model.create(data)
  166. if level >= 1 and level <= 4:
  167. self._parents[level - 1] = nuts.id
  168. return nuts
  169. @api.multi
  170. def run_import(self):
  171. nuts_model = self.env['res.partner.nuts'].\
  172. with_context(defer_parent_store_computation=True)
  173. self._load_countries()
  174. # All current NUTS (for available countries),
  175. # delete if not found above
  176. nuts_to_delete = nuts_model.search(
  177. [('country_id', 'in', [x.id for x in self._countries.values()])])
  178. # Download NUTS in english, create or update
  179. logger.info('Importing NUTS 2013 English...')
  180. xmlcontent = self._download_nuts()
  181. dom = etree.fromstring(xmlcontent)
  182. for node in dom.iter('Item'):
  183. logger.debug('Reading level=%s, id=%s',
  184. node.get('idLevel', 'N/A'),
  185. node.get('id', 'N/A'))
  186. nuts = self.create_or_update_nuts(node)
  187. if nuts and nuts in nuts_to_delete:
  188. nuts_to_delete -= nuts
  189. # Delete obsolete NUTS
  190. if nuts_to_delete:
  191. logger.info('%d NUTS entries deleted' % len(nuts_to_delete))
  192. nuts_to_delete.unlink()
  193. logger.info(
  194. 'The wizard to create NUTS entries from RAMON '
  195. 'has been successfully completed.')
  196. return True