[IMP] edi: updated metadata, improved error handling

bzr revid: odo@openerp.com-20111105004217-7s1ks15so5167lfp
This commit is contained in:
Olivier Dony 2011-11-05 01:42:17 +01:00
parent 4955615035
commit 00dc28d4b0
2 changed files with 46 additions and 50 deletions

View File

@ -41,7 +41,7 @@ class edi(netsvc.ExportService):
cr.commit() cr.commit()
except Exception: except Exception:
_logger.exception('Failed to execute EDI method %s with args %r', method_name, method_args) _logger.exception('Failed to execute EDI method %s with args %r', method_name, method_args)
cr.rollback() raise
finally: finally:
cr.close() cr.close()
return res return res

View File

@ -34,11 +34,13 @@ import netsvc
import pooler import pooler
from osv import osv,fields,orm from osv import osv,fields,orm
from tools.translate import _ from tools.translate import _
from tools.parse_version import parse_version
from tools.safe_eval import safe_eval as eval from tools.safe_eval import safe_eval as eval
EXTERNAL_ID_PATTERN = re.compile(r'^([^.:]+)(?::([^.]+))?\.(\S+)$') EXTERNAL_ID_PATTERN = re.compile(r'^([^.:]+)(?::([^.]+))?\.(\S+)$')
EDI_VIEW_WEB_URL = '%s/edi/view?debug=1&db=%s&token=%s' EDI_VIEW_WEB_URL = '%s/edi/view?debug=1&db=%s&token=%s'
EDI_PROTOCOL_VERSION = 1 # arbitrary ever-increasing version number
EDI_GENERATOR = 'OpenERP ' + release.major_version
EDI_GENERATOR_VERSION = release.version_info
def split_external_id(ext_id): def split_external_id(ext_id):
match = EXTERNAL_ID_PATTERN.match(ext_id) match = EXTERNAL_ID_PATTERN.match(ext_id)
@ -61,9 +63,6 @@ def safe_unique_id(database_id, model, record_id):
digest = base64.urlsafe_b64encode(digest) digest = base64.urlsafe_b64encode(digest)
return '%s-%s' % (model.replace('.','_'), digest) return '%s-%s' % (model.replace('.','_'), digest)
def version_tuple():
return parse_version(release.version)
def last_update_for(record): def last_update_for(record):
"""Returns the last update timestamp for the given record, """Returns the last update timestamp for the given record,
if available, otherwise False if available, otherwise False
@ -140,8 +139,9 @@ class edi_document(osv.osv):
:param edi_documents: list of Python dicts containing the deserialized :param edi_documents: list of Python dicts containing the deserialized
version of EDI documents version of EDI documents
:return: list of (model, id) pairs containing the model and database ID :return: list of (model, id, action) tuple containing the model and database ID
of all records that were imported in the system of all records that were imported in the system, plus a suggested
action definition dict for displaying each document.
""" """
ir_module = self.pool.get('ir.module.module') ir_module = self.pool.get('ir.module.module')
res = [] res = []
@ -158,7 +158,8 @@ class edi_document(osv.osv):
assert model_obj, 'model `%s` cannot be found, despite module `%s` being available - '\ assert model_obj, 'model `%s` cannot be found, despite module `%s` being available - '\
'this EDI document seems invalid or unsupported' % (model,module) 'this EDI document seems invalid or unsupported' % (model,module)
record_id = model_obj.edi_import(cr, uid, edi_document, context=context) record_id = model_obj.edi_import(cr, uid, edi_document, context=context)
res.append((model, record_id)) record_action = model_obj._edi_record_display_action(cr, uid, record_id, context=context)
res.append((model, record_id, record_action))
return res return res
def deserialize(self, edi_documents_string): def deserialize(self, edi_documents_string):
@ -282,6 +283,19 @@ class EDIMixin(object):
return '%s.%s' % (module, ext_id) return '%s.%s' % (module, ext_id)
def _edi_record_display_action(self, cr, uid, id, context=None):
"""Returns an appropriate action definition dict for displaying
the record with ID ``rec_id``.
:param int id: database ID of record to display
:return: action definition dict
"""
return {'type': 'ir.actions.act_window',
'view_mode': 'form,tree',
'view_type': 'form',
'res_model': self._name,
'res_id': id}
def edi_metadata(self, cr, uid, records, context=None): def edi_metadata(self, cr, uid, records, context=None):
"""Return a list containing the boilerplate EDI structures for """Return a list containing the boilerplate EDI structures for
exporting ``records`` as EDI, including exporting ``records`` as EDI, including
@ -294,7 +308,9 @@ class EDIMixin(object):
'__module': 'module', # require module '__module': 'module', # require module
'__id': 'module:db-uuid:model.id', # unique global external ID for the record '__id': 'module:db-uuid:model.id', # unique global external ID for the record
'__last_update': '2011-01-01 10:00:00', # last update date in UTC! '__last_update': '2011-01-01 10:00:00', # last update date in UTC!
'__version' : [6,1,0], # server version, to check compatibility. '__version': 1, # EDI spec version
'__generator' : 'OpenERP', # EDI generator
'__generator_version' : [6,1,0], # server version, to check compatibility.
'__attachments_': '__attachments_':
} }
@ -305,25 +321,27 @@ class EDIMixin(object):
data_ids = [] data_ids = []
ir_attachment = self.pool.get('ir.attachment') ir_attachment = self.pool.get('ir.attachment')
results = [] results = []
version = version_tuple()
for record in records: for record in records:
attachment_ids = ir_attachment.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)])
attachments = []
for attachment in ir_attachment.browse(cr, uid, attachment_ids, context=context):
attachments.append({
'name' : attachment.name,
'content': attachment.datas, # already base64 encoded!
'file_name': attachment.datas_fname,
})
ext_id = self._edi_external_id(cr, uid, record, context=context) ext_id = self._edi_external_id(cr, uid, record, context=context)
edi_dict = { edi_dict = {
'__id': ext_id, '__id': ext_id,
'__last_update': last_update_for(record), '__last_update': last_update_for(record),
'__model' : record._name, '__model' : record._name,
'__module' : record._original_module, '__module' : record._original_module,
'__version': version, '__version': EDI_PROTOCOL_VERSION,
'__attachments': attachments, '__generator': EDI_GENERATOR,
'__generator_version': EDI_GENERATOR_VERSION,
} }
attachment_ids = ir_attachment.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)])
if attachment_ids:
attachments = []
for attachment in ir_attachment.browse(cr, uid, attachment_ids, context=context):
attachments.append({
'name' : attachment.name,
'content': attachment.datas, # already base64 encoded!
'file_name': attachment.datas_fname,
})
edi_dict.update(__attachments=attachments)
results.append(edi_dict) results.append(edi_dict)
return results return results
@ -441,7 +459,7 @@ class EDIMixin(object):
db = pooler.get_db(cr.dbname) db = pooler.get_db(cr.dbname)
local_cr = None local_cr = None
try: try:
time.sleep(1) # FIXME: workaround to wait for commit of parent transaction time.sleep(3) # lame workaround to wait for commit of parent transaction
# grab a fresh browse_record on local cursor # grab a fresh browse_record on local cursor
local_cr = db.cursor() local_cr = db.cursor()
web_root_url = self.pool.get('ir.config_parameter').get_param(local_cr, uid, 'web.base.url') web_root_url = self.pool.get('ir.config_parameter').get_param(local_cr, uid, 'web.base.url')
@ -458,6 +476,7 @@ class EDIMixin(object):
edi_context = dict(context, edi_web_url_view=EDI_VIEW_WEB_URL % (web_root_url, local_cr.dbname, edi_token)) edi_context = dict(context, edi_web_url_view=EDI_VIEW_WEB_URL % (web_root_url, local_cr.dbname, edi_token))
self.pool.get('email.template').send_mail(local_cr, uid, mail_tmpl.id, edi_record.id, self.pool.get('email.template').send_mail(local_cr, uid, mail_tmpl.id, edi_record.id,
force_send=True, context=edi_context) force_send=True, context=edi_context)
_logger.info('EDI export successful for %s #%s, email notification sent.', self._name, edi_record.id)
except Exception: except Exception:
_logger.warning('Ignoring EDI mail notification, failed to generate it.', exc_info=True) _logger.warning('Ignoring EDI mail notification, failed to generate it.', exc_info=True)
finally: finally:
@ -594,33 +613,10 @@ class EDIMixin(object):
return target.id return target.id
def edi_import(self, cr, uid, edi_document, context=None): def edi_import(self, cr, uid, edi_document, context=None):
"""Imports a dict representing an edi.document into the system, """Imports a dict representing an edi.document into the system.
applying the following rules.
All relationship fields are exported in a special way, and provide their own :param dict edi_document: EDI document to import
unique identifier, so that we can avoid duplication of records when importing. :return: the database ID of the imported record
#. Many2One
See :meth:`~.edi_import_relation`
#. One2Many
O2M fields are always exported as a list of dicts, where each dict corresponds
to a full EDI record. The import should not update existing records
if they already exist, it should only link them to the parent object, in this
fashion:
* First import the parent object, using the usual procedire
* Look for a record that matches the db_id provided in the __id field. If
found, keep the corresponding database id, and connect it to the parent.
* If not found via db_id, create a new entry using the same method that
imports a full EDI record, grab the resulting db id, and use it to
connect to the parent.
#: Many2Many
M2M fields are always exported as a list of pairs similar to M2O.
For each pair in the M2M:
* Perform the same steps as for a Many2One (See :meth:`~.edi_import_relation`)
* After finding the database ID of the final record in the database,
connect it to the parent record.
""" """
assert self._name == edi_document.get('__import_model') or \ assert self._name == edi_document.get('__import_model') or \
('__import_model' not in edi_document and self._name == edi_document.get('__model')), \ ('__import_model' not in edi_document and self._name == edi_document.get('__model')), \
@ -629,10 +625,10 @@ class EDIMixin(object):
# First check the record is now already known in the database, in which case it is ignored # First check the record is now already known in the database, in which case it is ignored
ext_id_members = split_external_id(edi_document['__id']) ext_id_members = split_external_id(edi_document['__id'])
existing_id = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context) existing = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context)
if existing_id: if existing:
_logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full']) _logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full'])
return existing_id.id return existing.id
record_values = {} record_values = {}
o2m_todo = {} # o2m values are processed after their parent already exists o2m_todo = {} # o2m values are processed after their parent already exists