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.

205 lines
7.0 KiB

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