1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Business Applications
5 # Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
32 import openerp.release as release
35 from osv import osv,fields,orm
36 from tools.translate import _
37 from tools.safe_eval import safe_eval as eval
39 EXTERNAL_ID_PATTERN = re.compile(r'^([^.:]+)(?::([^.]+))?\.(\S+)$')
40 EDI_VIEW_WEB_URL = '%s/edi/view?db=%s&token=%s'
41 EDI_PROTOCOL_VERSION = 1 # arbitrary ever-increasing version number
42 EDI_GENERATOR = 'OpenERP ' + release.major_version
43 EDI_GENERATOR_VERSION = release.version_info
45 def split_external_id(ext_id):
46 match = EXTERNAL_ID_PATTERN.match(ext_id)
48 _("'%s' is an invalid external ID") % (ext_id)
49 return {'module': match.group(1),
50 'db_uuid': match.group(2),
52 'full': match.group(0)}
54 def safe_unique_id(database_id, model, record_id):
55 """Generate a unique string to represent a (database_uuid,model,record_id) pair
56 without being too long, and with a very low probability of collisions.
58 msg = "%s-%s-%s-%s" % (time.time(), database_id, model, record_id)
59 digest = hashlib.sha1(msg).digest()
60 # fold the sha1 20 bytes digest to 9 bytes
61 digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2]))
62 # b64-encode the 9-bytes folded digest to a reasonable 12 chars ASCII ID
63 digest = base64.urlsafe_b64encode(digest)
64 return '%s-%s' % (model.replace('.','_'), digest)
66 def last_update_for(record):
67 """Returns the last update timestamp for the given record,
68 if available, otherwise False
70 if record._model._log_access:
71 record_log = record.perm_read()[0]
72 return record_log.get('write_date') or record_log.get('create_date') or False
75 _logger = logging.getLogger('edi')
77 class edi_document(osv.osv):
78 _name = 'edi.document'
79 _description = 'EDI Document'
81 'name': fields.char("EDI token", size = 128, help="Unique identifier for retrieving an EDI document."),
82 'document': fields.text("Document", help="EDI document content")
85 ('name_uniq', 'unique (name)', 'EDI Tokens must be unique!')
88 def new_edi_token(self, cr, uid, record):
89 """Return a new, random unique token to identify this model record,
90 and to be used as token when exporting it as an EDI document.
92 :param browse_record record: model record for which a token is needed
94 db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
95 edi_token = hashlib.sha256('%s-%s-%s-%s' % (time.time(), db_uuid, record._name, record.id)).hexdigest()
98 def serialize(self, edi_documents):
99 """Serialize the given EDI document structures (Python dicts holding EDI data),
100 using JSON serialization.
102 :param [dict] edi_documents: list of EDI document structures to serialize
103 :return: UTF-8 encoded string containing the serialized document
105 serialized_list = json.dumps(edi_documents)
106 return serialized_list
108 def generate_edi(self, cr, uid, records, context=None):
109 """Generates a final EDI document containing the EDI serialization
110 of the given records, which should all be instances of a Model
111 that has the :meth:`~.edi` mixin. The document is not saved in the
112 database, this is done by :meth:`~.export_edi`.
114 :param list(browse_record) records: records to export as EDI
115 :return: UTF-8 encoded string containing the serialized records
118 for record in records:
119 record_model_obj = self.pool.get(record._name)
120 edi_list += record_model_obj.edi_export(cr, uid, [record], context=context)
121 return self.serialize(edi_list)
123 def get_document(self, cr, uid, edi_token, context=None):
124 """Retrieve the EDI document corresponding to the given edi_token.
126 :return: EDI document string
127 :raise: ValueError if requested EDI token does not match any know document
129 _logger.debug("get_document(%s)", edi_token)
130 edi_ids = self.search(cr, uid, [('name','=', edi_token)], context=context)
132 raise ValueError('Invalid EDI token: %s' % edi_token)
133 edi = self.browse(cr, uid, edi_ids[0], context=context)
136 def load_edi(self, cr, uid, edi_documents, context=None):
137 """Import the given EDI document structures into the system, using
138 :meth:`~.import_edi`.
140 :param edi_documents: list of Python dicts containing the deserialized
141 version of EDI documents
142 :return: list of (model, id, action) tuple containing the model and database ID
143 of all records that were imported in the system, plus a suggested
144 action definition dict for displaying each document.
146 ir_module = self.pool.get('ir.module.module')
148 for edi_document in edi_documents:
149 module = edi_document.get('__import_module') or edi_document.get('__module')
150 assert module, 'a `__module` or `__import_module` attribute is required in each EDI document'
151 if module != 'base' and not ir_module.search(cr, uid, [('name','=',module),('state','=','installed')]):
152 raise osv.except_osv(_('Missing Application'),
153 _("The document you are trying to import requires the OpenERP `%s` application. "
154 "You can install it by connecting as the administrator and opening the configuration assistant.")%(module,))
155 model = edi_document.get('__import_model') or edi_document.get('__model')
156 assert model, 'a `__model` or `__import_model` attribute is required in each EDI document'
157 model_obj = self.pool.get(model)
158 assert model_obj, 'model `%s` cannot be found, despite module `%s` being available - '\
159 'this EDI document seems invalid or unsupported' % (model,module)
160 record_id = model_obj.edi_import(cr, uid, edi_document, context=context)
161 record_action = model_obj._edi_record_display_action(cr, uid, record_id, context=context)
162 res.append((model, record_id, record_action))
165 def deserialize(self, edi_documents_string):
166 """Return deserialized version of the given EDI Document string.
168 :param str|unicode edi_documents_string: UTF-8 string (or unicode) containing
169 JSON-serialized EDI document(s)
170 :return: Python object representing the EDI document(s) (usually a list of dicts)
172 return json.loads(edi_documents_string)
174 def export_edi(self, cr, uid, records, context=None):
175 """Export the given database records as EDI documents, stores them
176 permanently with a new unique EDI token, for later retrieval via :meth:`~.get_document`,
177 and returns the list of the new corresponding ``ir.edi.document`` records.
179 :param records: list of browse_record of any model
180 :return: list of IDs of the new ``ir.edi.document`` entries, in the same
181 order as the provided ``records``.
184 for record in records:
185 document = self.generate_edi(cr, uid, [record], context)
186 token = self.new_edi_token(cr, uid, record)
187 self.create(cr, uid, {
191 exported_ids.append(token)
194 def import_edi(self, cr, uid, edi_document=None, edi_url=None, context=None):
195 """Import a JSON serialized EDI Document string into the system, first retrieving it
196 from the given ``edi_url`` if provided.
198 :param str|unicode edi_document: UTF-8 string or unicode containing JSON-serialized
199 EDI Document to import. Must not be provided if
200 ``edi_url`` is given.
201 :param str|unicode edi_url: URL where the EDI document (same format as ``edi_document``)
202 may be retrieved, without authentication.
205 assert not edi_document, 'edi_document must not be provided if edi_url is given'
206 edi_document = urllib2.urlopen(edi_url).read()
207 assert edi_document, 'EDI Document is empty!'
208 edi_documents = self.deserialize(edi_document)
209 return self.load_edi(cr, uid, edi_documents, context=context)
212 class EDIMixin(object):
213 """Mixin class for Model objects that want be exposed as EDI documents.
214 Classes that inherit from this mixin class should override the
215 ``edi_import()`` and ``edi_export()`` methods to implement their
216 specific behavior, based on the primitives provided by this mixin."""
218 def _edi_requires_attributes(self, attributes, edi_document):
219 model_name = edi_document.get('__imported_model') or edi_document.get('__model') or self._name
220 for attribute in attributes:
221 assert edi_document.get(attribute),\
222 'Attribute `%s` is required in %s EDI documents' % (attribute, model_name)
224 # private method, not RPC-exposed as it creates ir.model.data entries as
225 # SUPERUSER based on its parameters
226 def _edi_external_id(self, cr, uid, record, existing_id=None, existing_module=None,
228 """Generate/Retrieve unique external ID for ``record``.
229 Each EDI record and each relationship attribute in it is identified by a
230 unique external ID, which includes the database's UUID, as a way to
231 refer to any record within any OpenERP instance, without conflict.
233 For OpenERP records that have an existing "External ID" (i.e. an entry in
234 ir.model.data), the EDI unique identifier for this record will be made of
235 "%s:%s:%s" % (module, database UUID, ir.model.data ID). The database's
236 UUID MUST NOT contain a colon characters (this is guaranteed by the
239 For records that have no existing ir.model.data entry, a new one will be
240 created during the EDI export. It is recommended that the generated external ID
241 contains a readable reference to the record model, plus a unique value that
242 hides the database ID. If ``existing_id`` is provided (because it came from
243 an import), it will be used instead of generating a new one.
244 If ``existing_module`` is provided (because it came from
245 an import), it will be used instead of using local values.
247 :param browse_record record: any browse_record needing an EDI external ID
248 :param string existing_id: optional existing external ID value, usually coming
249 from a just-imported EDI record, to be used instead
250 of generating a new one
251 :param string existing_module: optional existing module name, usually in the
252 format ``module:db_uuid`` and coming from a
253 just-imported EDI record, to be used instead
255 :return: the full unique External ID to use for record
257 ir_model_data = self.pool.get('ir.model.data')
258 db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
259 ext_id = record.get_external_id()[record.id]
261 ext_id = existing_id or safe_unique_id(db_uuid, record._name, record.id)
262 # ID is unique cross-db thanks to db_uuid (already included in existing_module)
263 module = existing_module or "%s:%s" % (record._original_module, db_uuid)
264 _logger.debug("%s: Generating new external ID `%s.%s` for %r", self._name,
265 module, ext_id, record)
266 ir_model_data.create(cr, openerp.SUPERUSER_ID,
268 'model': record._name,
270 'res_id': record.id})
272 module, ext_id = ext_id.split('.')
273 if not ':' in module:
274 # this record was not previously EDI-imported
275 if not module == record._original_module:
276 # this could happen for data records defined in a module that depends
277 # on the module that owns the model, e.g. purchase defines
278 # product.pricelist records.
279 _logger.debug('Mismatching module: expected %s, got %s, for %s',
280 module, record._original_module, record)
281 # ID is unique cross-db thanks to db_uuid
282 module = "%s:%s" % (module, db_uuid)
284 return '%s.%s' % (module, ext_id)
286 def _edi_record_display_action(self, cr, uid, id, context=None):
287 """Returns an appropriate action definition dict for displaying
288 the record with ID ``rec_id``.
290 :param int id: database ID of record to display
291 :return: action definition dict
293 return {'type': 'ir.actions.act_window',
294 'view_mode': 'form,tree',
296 'res_model': self._name,
299 def edi_metadata(self, cr, uid, records, context=None):
300 """Return a list containing the boilerplate EDI structures for
301 exporting ``records`` as EDI, including
304 The metadata fields always include::
307 '__model': 'some.model', # record model
308 '__module': 'module', # require module
309 '__id': 'module:db-uuid:model.id', # unique global external ID for the record
310 '__last_update': '2011-01-01 10:00:00', # last update date in UTC!
311 '__version': 1, # EDI spec version
312 '__generator' : 'OpenERP', # EDI generator
313 '__generator_version' : [6,1,0], # server version, to check compatibility.
317 :param list(browse_record) records: records to export
318 :return: list of dicts containing boilerplate EDI metadata for each record,
319 at the corresponding index from ``records``.
322 ir_attachment = self.pool.get('ir.attachment')
324 for record in records:
325 ext_id = self._edi_external_id(cr, uid, record, context=context)
328 '__last_update': last_update_for(record),
329 '__model' : record._name,
330 '__module' : record._original_module,
331 '__version': EDI_PROTOCOL_VERSION,
332 '__generator': EDI_GENERATOR,
333 '__generator_version': EDI_GENERATOR_VERSION,
335 attachment_ids = ir_attachment.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)])
338 for attachment in ir_attachment.browse(cr, uid, attachment_ids, context=context):
340 'name' : attachment.name,
341 'content': attachment.datas, # already base64 encoded!
342 'file_name': attachment.datas_fname,
344 edi_dict.update(__attachments=attachments)
345 results.append(edi_dict)
348 def edi_m2o(self, cr, uid, record, context=None):
349 """Return a m2o EDI representation for the given record.
351 The EDI format for a many2one is::
353 ['unique_external_id', 'Document Name']
355 edi_ext_id = self._edi_external_id(cr, uid, record, context=context)
356 relation_model = record._model
357 name = relation_model.name_get(cr, uid, [record.id], context=context)
358 name = name and name[0][1] or False
359 return [edi_ext_id, name]
361 def edi_o2m(self, cr, uid, records, edi_struct=None, context=None):
362 """Return a list representing a O2M EDI relationship containing
363 all the given records, according to the given ``edi_struct``.
364 This is basically the same as exporting all the record using
365 :meth:`~.edi_export` with the given ``edi_struct``, and wrapping
366 the results in a list.
370 [ # O2M fields would be a list of dicts, with their
371 { '__id': 'module:db-uuid.id', # own __id.
372 '__last_update': 'iso date', # update date
380 for record in records:
381 result += record._model.edi_export(cr, uid, [record], edi_struct=edi_struct, context=context)
384 def edi_m2m(self, cr, uid, records, context=None):
385 """Return a list representing a M2M EDI relationship directed towards
386 all the given records.
387 This is basically the same as exporting all the record using
388 :meth:`~.edi_m2o` and wrapping the results in a list.
392 # M2M fields are exported as a list of pairs, like a list of M2O values
394 ['module:db-uuid.id1', 'Task 01: bla bla'],
395 ['module:db-uuid.id2', 'Task 02: bla bla']
398 return [self.edi_m2o(cr, uid, r, context=context) for r in records]
400 def edi_export(self, cr, uid, records, edi_struct=None, context=None):
401 """Returns a list of dicts representing an edi.document containing the
402 records, and matching the given ``edi_struct``, if provided.
404 :param edi_struct: if provided, edi_struct should be a dictionary
405 with a skeleton of the fields to export.
406 Basic fields can have any key as value, but o2m
407 values should have a sample skeleton dict as value,
408 to act like a recursive export.
409 For example, for a res.partner record::
420 Any field not specified in the edi_struct will not
421 be included in the exported data. Fields with no
422 value (False) will be omitted in the EDI struct.
423 If edi_struct is omitted, no fields will be exported
425 if edi_struct is None:
427 fields_to_export = edi_struct.keys()
429 for record in records:
430 edi_dict = self.edi_metadata(cr, uid, [record], context=context)[0]
431 for field in fields_to_export:
432 column = self._all_columns[field].column
433 value = getattr(record, field)
434 if not value and value not in ('', 0):
436 elif column._type == 'many2one':
437 value = self.edi_m2o(cr, uid, value, context=context)
438 elif column._type == 'many2many':
439 value = self.edi_m2m(cr, uid, value, context=context)
440 elif column._type == 'one2many':
441 value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field, {}), context=context)
442 edi_dict[field] = value
443 results.append(edi_dict)
446 def edi_export_and_email(self, cr, uid, ids, template_ext_id, context=None):
447 """Export the given records just like :meth:`~.export_edi`, the render the
448 given email template, in order to trigger appropriate notifications.
449 This method is intended to be called as part of business documents'
450 lifecycle, so it silently ignores any error occurring during the process,
451 as this is usually non-critical. To avoid any delay, it is also asynchronous
452 and will spawn a short-lived thread to perform the action.
454 :param str template_ext_id: external id of the email.template to use for
455 the mail notifications
459 db = pooler.get_db(cr.dbname)
462 # lame workaround to wait for commit of parent transaction
463 wait_try, wait_max_try = 0, 50
464 while not cr._Cursor__closed and wait_try < wait_max_try:
467 # grab a fresh browse_record on local cursor
468 local_cr = db.cursor()
469 web_root_url = self.pool.get('ir.config_parameter').get_param(local_cr, uid, 'web.base.url')
471 _logger.warning('Ignoring EDI mail notification, web.base.url not defined in parameters')
473 mail_tmpl = self._edi_get_object_by_external_id(local_cr, uid, template_ext_id, 'email.template', context=context)
475 # skip EDI export if the template was not found
476 _logger.warning('Ignoring EDI mail notification, template %s cannot be located', template_ext_id)
478 for edi_record in self.browse(local_cr, uid, ids, context=context):
479 edi_context = dict(context, edi_web_url_view=self._edi_get_object_web_url_view(local_cr, uid, edi_record, context=context))
480 self.pool.get('email.template').send_mail(local_cr, uid, mail_tmpl.id, edi_record.id,
481 force_send=False, context=edi_context)
482 _logger.info('EDI export successful for %s #%s, email notification sent.', self._name, edi_record.id)
484 _logger.warning('Ignoring EDI mail notification, failed to generate it.', exc_info=True)
490 threading.Thread(target=email_task, name='EDI ExportAndEmail for %s %r' % (self._name, ids)).start()
493 def _edi_get_object_web_url_view(self, cr, uid, record, context=None):
494 web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
496 _logger.warning('Ignoring EDI mail notification, web.base.url not defined in parameters')
498 edi_token = self.pool.get('edi.document').export_edi(cr, uid, [record], context=context)[0]
499 return EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token)
501 def _edi_get_object_by_name(self, cr, uid, name, model_name, context=None):
502 model = self.pool.get(model_name)
503 search_results = model.name_search(cr, uid, name, operator='=', context=context)
504 if len(search_results) == 1:
505 return model.browse(cr, uid, search_results[0][0], context=context)
508 def _edi_generate_report_attachment(self, cr, uid, record, context=None):
509 """Utility method to generate the first PDF-type report declared for the
510 current model with ``usage`` attribute set to ``default``.
511 This must be called explicitly by models that need it, usually
512 at the beginning of ``edi_export``, before the call to ``super()``."""
513 ir_actions_report = self.pool.get('ir.actions.report.xml')
514 matching_reports = ir_actions_report.search(cr, uid, [('model','=',self._name),
515 ('report_type','=','pdf'),
516 ('usage','=','default')])
518 report = ir_actions_report.browse(cr, uid, matching_reports[0])
519 report_service = 'report.' + report.report_name
520 service = netsvc.LocalService(report_service)
521 (result, format) = service.create(cr, uid, [record.id], {'model': self._name}, context=context)
522 eval_context = {'time': time, 'object': record}
523 if not report.attachment or not eval(report.attachment, eval_context):
524 # no auto-saving of report as attachment, need to do it manually
525 result = base64.b64encode(result)
526 file_name = record.name_get()[0][1]
527 file_name = re.sub(r'[^a-zA-Z0-9_-]', '_', file_name)
529 ir_attachment = self.pool.get('ir.attachment').create(cr, uid,
532 'datas_fname': file_name,
533 'res_model': self._name,
534 'res_id': record.id},
537 def _edi_import_attachments(self, cr, uid, record_id, edi_document, context=None):
538 ir_attachment = self.pool.get('ir.attachment')
539 for attachment in edi_document.get('__attachments', []):
540 # check attachment data is non-empty and valid
543 file_data = base64.b64decode(attachment.get('content'))
546 assert file_data, 'Incorrect/Missing attachment file content'
547 assert attachment.get('name'), 'Incorrect/Missing attachment name'
548 assert attachment.get('file_name'), 'Incorrect/Missing attachment file name'
549 assert attachment.get('file_name'), 'Incorrect/Missing attachment file name'
550 ir_attachment.create(cr, uid, {'name': attachment['name'],
551 'datas_fname': attachment['file_name'],
552 'res_model': self._name,
554 # should be pure 7bit ASCII
555 'datas': str(attachment['content']),
559 def _edi_get_object_by_external_id(self, cr, uid, external_id, model, context=None):
560 """Returns browse_record representing object identified by the model and external_id,
561 or None if no record was found with this external id.
563 :param external_id: fully qualified external id, in the EDI form
564 ``module:db_uuid:identifier``.
565 :param model: model name the record belongs to.
567 ir_model_data = self.pool.get('ir.model.data')
568 # external_id is expected to have the form: ``module:db_uuid:model.random_name``
569 ext_id_members = split_external_id(external_id)
570 db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
571 module = ext_id_members['module']
572 ext_id = ext_id_members['id']
574 ext_db_uuid = ext_id_members['db_uuid']
576 modules.append('%s:%s' % (module, ext_id_members['db_uuid']))
577 if ext_db_uuid is None or ext_db_uuid == db_uuid:
578 # local records may also be registered without the db_uuid
579 modules.append(module)
580 data_ids = ir_model_data.search(cr, uid, [('model','=',model),
582 ('module','in',modules)])
584 model = self.pool.get(model)
585 data = ir_model_data.browse(cr, uid, data_ids[0], context=context)
586 result = model.browse(cr, uid, data.res_id, context=context)
589 def edi_import_relation(self, cr, uid, model, value, external_id, context=None):
590 """Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the
591 given model, returning the corresponding database ID:
593 * First, checks if the ``external_id`` is already known, in which case the corresponding
594 database ID is directly returned, without doing anything else;
595 * If the ``external_id`` is unknown, attempts to locate an existing record
596 with the same ``value`` via name_search(). If found, the given external_id will
597 be assigned to this local record (in addition to any existing one)
598 * If previous steps gave no result, create a new record with the given
599 value in the target model, assign it the given external_id, and return
602 _logger.debug("%s: Importing EDI relationship [%r,%r]", model, external_id, value)
603 target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context)
604 need_new_ext_id = False
606 _logger.debug("%s: Importing EDI relationship [%r,%r] - ID not found, trying name_get",
607 self._name, external_id, value)
608 target = self._edi_get_object_by_name(cr, uid, value, model, context=context)
609 need_new_ext_id = True
611 _logger.debug("%s: Importing EDI relationship [%r,%r] - name not found, creating it!",
612 self._name, external_id, value)
613 # also need_new_ext_id here, but already been set above
614 model = self.pool.get(model)
615 # should use name_create() but e.g. res.partner won't allow it at the moment
616 res_id = model.create(cr, uid, {model._rec_name: value}, context=context)
617 target = model.browse(cr, uid, res_id, context=context)
619 ext_id_members = split_external_id(external_id)
620 # module name is never used bare when creating ir.model.data entries, in order
621 # to avoid being taken as part of the module's data, and cleanup up at next update
622 module = "%s:%s" % (ext_id_members['module'], ext_id_members['db_uuid'])
623 # create a new ir.model.data entry for this value
624 self._edi_external_id(cr, uid, target, existing_id=ext_id_members['id'], existing_module=module, context=context)
627 def edi_import(self, cr, uid, edi_document, context=None):
628 """Imports a dict representing an edi.document into the system.
630 :param dict edi_document: EDI document to import
631 :return: the database ID of the imported record
633 assert self._name == edi_document.get('__import_model') or \
634 ('__import_model' not in edi_document and self._name == edi_document.get('__model')), \
635 "EDI Document Model and current model do not match: '%s' (EDI) vs '%s' (current)" % \
636 (edi_document['__model'], self._name)
638 # First check the record is now already known in the database, in which case it is ignored
639 ext_id_members = split_external_id(edi_document['__id'])
640 existing = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context)
642 _logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full'])
646 o2m_todo = {} # o2m values are processed after their parent already exists
647 for field_name, field_value in edi_document.iteritems():
648 # skip metadata and empty fields
649 if field_name.startswith('__') or field_value is None or field_value is False:
651 field_info = self._all_columns.get(field_name)
653 _logger.warning('Ignoring unknown field `%s` when importing `%s` EDI document', field_name, self._name)
655 field = field_info.column
656 # skip function/related fields
657 if isinstance(field, fields.function):
658 _logger.warning("Unexpected function field value found in '%s' EDI document: '%s'" % (self._name, field_name))
660 relation_model = field._obj
661 if field._type == 'many2one':
662 record_values[field_name] = self.edi_import_relation(cr, uid, relation_model,
663 field_value[1], field_value[0],
665 elif field._type == 'many2many':
666 record_values[field_name] = [self.edi_import_relation(cr, uid, relation_model, m2m_value[1],
667 m2m_value[0], context=context)
668 for m2m_value in field_value]
669 elif field._type == 'one2many':
670 # must wait until parent report is imported, as the parent relationship
671 # is often required in o2m child records
672 o2m_todo[field_name] = field_value
674 record_values[field_name] = field_value
676 module_ref = "%s:%s" % (ext_id_members['module'], ext_id_members['db_uuid'])
677 record_id = self.pool.get('ir.model.data')._update(cr, uid, self._name, module_ref, record_values,
678 xml_id=ext_id_members['id'], context=context)
680 record_display, = self.name_get(cr, uid, [record_id], context=context)
682 # process o2m values, connecting them to their parent on-the-fly
683 for o2m_field, o2m_value in o2m_todo.iteritems():
684 field = self._all_columns[o2m_field].column
685 dest_model = self.pool.get(field._obj)
686 for o2m_line in o2m_value:
687 # link to parent record: expects an (ext_id, name) pair
688 o2m_line[field._fields_id] = (ext_id_members['full'], record_display[1])
689 dest_model.edi_import(cr, uid, o2m_line, context=context)
691 # process the attachments, if any
692 self._edi_import_attachments(cr, uid, record_id, edi_document, context=context)
696 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: