From 48dbf92a9c0751af0d27af0890e4d19275ab2027 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Fri, 27 May 2011 12:55:23 +0530 Subject: [PATCH 01/56] [EDI]: develop a ir_edi_document class bzr revid: aar@tinyerp.com-20110527072523-v93lpjnpxm765bqd --- openerp/addons/base/ir/__init__.py | 2 +- openerp/addons/base/ir/ir_edi.py | 215 +++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 openerp/addons/base/ir/ir_edi.py diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index 9b4eb96df5f..673c4f7e0ab 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -36,6 +36,6 @@ import ir_rule import wizard import ir_config_parameter import osv_memory_autovacuum - +import ir_edi # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py new file mode 100644 index 00000000000..8b980d4cf4c --- /dev/null +++ b/openerp/addons/base/ir/ir_edi.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import osv,fields +import json +import hashlib +import time +import base64 +import urllib2 + +class ir_edi_document(osv.osv): + _name = 'ir.edi.document' + _description = 'ir_edi_document' + _columns = { + 'name': fields.char('EDI token', size = 128), + 'document': fields.text() + + } + + def safe_unique_id(model, database_id): + + """Generate a unique string to represent a (model,database id) pair + without being too long, without revealing the database id, and + with a very low probability of collisions. + """ + msg = "%s-%s-%s" % (model, time.time(), database_id) + digest = hashlib.sha1(msg).digest() + digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2])) + # finally, use the b64-encoded folded digest as ID part of the unique ID: + digest = base64.urlsafe_b64encode(digest) + + return '%s' % (digest) + + def new_edi_token(self,cr,uid,ids,browse_records,database_id,context=None): + + """Generate new unique tokent value to uniquely identify each edi_document""" + + db_uuid = self.safe_unique_id(browse_records, database_id) + edi_token = hashlib.sha256('%s-%s-%s' % (time.time(),db_uuid,time.time())).hexdigest() + + + return edi_token + + def serialize(self,edi_list_of_dicts): + + """Serialize the list of dictionaries using json dumps method""" + + serialized_list = json.dumps(edi_list_of_dicts) + return serialized_list + + def generate_edi(self,browse_record_list): + + """ Generate the list of dictionaries using edi_export method of edi class """ + + edi = edi() + edi_list = [] + for record in browse_record_list: + edi_struct = edi.edi_export(record) + edi_list.append(edi_struct[0]) + return self.serialize(edi_list) + + def get_document(self,cr,uid,ids,edi_token,context=None): + + """Get the edi document from database using given edi token """ + + records = self.search(cr,uid,[('name','=',edi_token)],context=context) + if records: + doc_string = self.read(cr,uid,records,['document'],context=context) + else: + raise osv.except_osv(_('Error !'), + _('Desired EDI Document does not Exist')) + + return True + + def load_edi(self,cr,uid,ids,edi_list_of_dicts,context=None): + + """loads the values from list of dictionaries to the corresponding OpenERP records + using the edi_import method of edi class""" + edi = edi() + edi_list = [] + for record in edi_list_of_dicts: + edi_record = edi.edi_import(record) + + + return True + + def deserialize(self,edi_document_string): + """ Deserialized the edi document string""" + + deserialize_string = json.loads(edi_document_string) + return self.load_edi(deserialize_string) + + def export_doc(self, cr, uid, ids,browse_records,database_id,context=None): + """the method handles the flow of the edi document generation and store it in + the database and return the edi_token of the particular document""" + + edi_document = { + 'name': self.new_edi_token(browse_records,database_id), + 'document': self.generate_edi(browse_records) + } + + unique_id = self.create(cr,uid,edi_document,context=context) + return edi_document['name'] + + def import_doc(self, cr, uid, ids, edi_document=None,edi_url=None,context=None): + + """the method handles the flow of importing particular edi document and + updates the database values on the basis of the edi document using + edi_loads method """ + + if edi_document is not None: + self.deserialize(edi_document) + elif edi_url is not None: + edi_document = urllib2.urlopen(edi_url).read() + self.deserialize(edi_document) + + return True + +ir_edi_document() + + +class edi(object): + _name = 'edi' + _description = 'edi document handler' + + """Mixin class for OSV objects that want be exposed as EDI documents. + Classes that inherit from this mixin class should override the + ``edi_import()`` and ``edi_export()`` methods to implement their + specific behavior, based on the primitives provided by this superclass.""" + + + def edi_metadata(self, cr, uid, browse_rec_list, context=None): + """Return a list representing the boilerplate EDI structure for + exporting the record in the given browse_rec_list, including + the metadata fields""" + # generic implementation! + return + + def edi_m2o(self, browse_rec, context=None): + """Return a list representing a M2O EDI value for + the given browse_record.""" + # generic implementation! + return + + def edi_o2m(self, browse_rec_list, context=None): + """Return a list representing a O2M EDI value for + the browse_records from the given browse_record_list.""" + # generic implementation! + return + + def edi_m2m(self, browse_rec_list, context=None): + """Return a list representing a M2M EDI value for + the browse_records from the given browse_record_list.""" + # generic implementation! + return + + def edi_export(self, cr, uid, ids, edi_struct=None, context=None): + """Returns a list of dicts representing an edi.document containing the + browse_records with ``ids``, using the generic algorithm. + :param edi_struct: if provided, edi_struct should be a dictionary + with a skeleton of the OSV fields to export as edi. + Basic fields can have any key as value, but o2m + values should have a sample skeleton dict as value. + For example, for a res.partner record: + edi_struct: { + 'name': True, + 'company_id': True, + 'address': { + 'name': True, + 'street': True, + } + } + Any field not specified in the edi_struct will not + be included in the exported data. + """ + # generic implementation! + + edi_struct = { + 'Openerp_fields':'', + '__model':'', + '__module':'', + '__id':'', + '__last_update':'', + '__version':'', + '__attachments':'', + + } + + + + return edi_struct + def edi_import(self, cr, uid, edi_document, context=None): + """Imports a list of dicts representing an edi.document, using the + generic algorithm. + """ + # generic implementation! + pass From 2e7ee8692142df851053967954e8eb55ffb13203 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Fri, 27 May 2011 19:01:31 +0530 Subject: [PATCH 02/56] [EDI]: working on a edi class bzr revid: aar@tinyerp.com-20110527133131-a1romjw8fft97950 --- openerp/addons/base/ir/ir_edi.py | 35 +++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 8b980d4cf4c..2187b4f89ca 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -74,7 +74,7 @@ class ir_edi_document(osv.osv): edi_list = [] for record in browse_record_list: edi_struct = edi.edi_export(record) - edi_list.append(edi_struct[0]) + edi_list.append(edi_struct) return self.serialize(edi_list) def get_document(self,cr,uid,ids,edi_token,context=None): @@ -193,8 +193,8 @@ class edi(object): """ # generic implementation! - edi_struct = { - 'Openerp_fields':'', + self.edi_document = { + '__model':'', '__module':'', '__id':'', @@ -203,10 +203,31 @@ class edi(object): '__attachments':'', } - - - - return edi_struct + data_object = self.pool.get('ir.model.data') + fields_object = self.pool.get('ir.model.fields') + for field in edi_struct.keys(): + if field in self.edi_document.keys(): + if field == 'model': + model_name = edi_struct['model'] + + record_ids = data_object.search(cr,uid,[('model','=','hr.holidays')]) + for fname in data_object.browse(cr,uid,record_ids): + if fname.name: + xml_ID = fname.name + self.edi_metadata(field,model_name) + else: + self.metadata(field) + else: + + f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',model_name)],context=context) + for fname in fields_object.browse(cr,uid,f_ids): + if fname.ttype == 'many2one': + self.edi_document[field] = self.edi_m2o(edi_struct[field]) + elif fname.ttype == 'one2many': + self.edi_ducument[field] = self.edi_o2m(edi_struct[field]) + elif fname.ttype == 'many2many': + self.edi_document[field] = self.edi_m2m(edi_struct[field]) + return self.edi_document def edi_import(self, cr, uid, edi_document, context=None): """Imports a list of dicts representing an edi.document, using the generic algorithm. From 2853ca743f27769f67803e405353c8656d7600b6 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Sat, 28 May 2011 19:09:43 +0530 Subject: [PATCH 03/56] [EDI]: working on a edi class bzr revid: aar@tinyerp.com-20110528133943-1r1hle6k75dvr17o --- openerp/addons/base/ir/ir_edi.py | 37 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 2187b4f89ca..251108cbf04 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -147,12 +147,16 @@ class edi(object): specific behavior, based on the primitives provided by this superclass.""" - def edi_metadata(self, cr, uid, browse_rec_list, context=None): + def edi_metadata(self, cr, uid, browse_rec_list,model=None, context=None): """Return a list representing the boilerplate EDI structure for exporting the record in the given browse_rec_list, including the metadata fields""" # generic implementation! - return + if model is not None: + self.edi_document['__model'] = model + + + return True def edi_m2o(self, browse_rec, context=None): """Return a list representing a M2O EDI value for @@ -195,28 +199,30 @@ class edi(object): self.edi_document = { - '__model':'', - '__module':'', - '__id':'', - '__last_update':'', - '__version':'', - '__attachments':'', + 'model':'', + 'module':'', + 'id':'', + 'last_update':'', + 'version':'', + 'attachments':'', } + if 'model' not in edi_struct.keys(): + return False data_object = self.pool.get('ir.model.data') + fields_object = self.pool.get('ir.model.fields') - for field in edi_struct.keys(): - if field in self.edi_document.keys(): + + for field in self.edi_document.keys(): + + if field in edi_struct.keys(): if field == 'model': model_name = edi_struct['model'] - - record_ids = data_object.search(cr,uid,[('model','=','hr.holidays')]) + record_ids = data_object.search(cr,uid,[('model','=',model_name)]) for fname in data_object.browse(cr,uid,record_ids): if fname.name: xml_ID = fname.name - self.edi_metadata(field,model_name) - else: - self.metadata(field) + else: f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',model_name)],context=context) @@ -229,6 +235,7 @@ class edi(object): self.edi_document[field] = self.edi_m2m(edi_struct[field]) return self.edi_document def edi_import(self, cr, uid, edi_document, context=None): + """Imports a list of dicts representing an edi.document, using the generic algorithm. """ From d6f19568d0b08c2b2110a357fa443d176c9c515e Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Mon, 30 May 2011 19:04:28 +0530 Subject: [PATCH 04/56] [EDI]: working on edi import bzr revid: aar@tinyerp.com-20110530133428-v68yubyv3rb9r4xb --- openerp/addons/base/ir/ir_edi.py | 161 ++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 54 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 251108cbf04..9d66d4ed866 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -26,6 +26,20 @@ import time import base64 import urllib2 +def safe_unique_id(model, database_id): + + """Generate a unique string to represent a (model,database id) pair + without being too long, without revealing the database id, and + with a very low probability of collisions. + """ + msg = "%s-%s-%s" % (model, time.time(), database_id) + digest = hashlib.sha1(msg).digest() + digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2])) + # finally, use the b64-encoded folded digest as ID part of the unique ID: + digest = base64.urlsafe_b64encode(digest) + + return '%s' % (digest) + class ir_edi_document(osv.osv): _name = 'ir.edi.document' _description = 'ir_edi_document' @@ -35,25 +49,13 @@ class ir_edi_document(osv.osv): } - def safe_unique_id(model, database_id): - - """Generate a unique string to represent a (model,database id) pair - without being too long, without revealing the database id, and - with a very low probability of collisions. - """ - msg = "%s-%s-%s" % (model, time.time(), database_id) - digest = hashlib.sha1(msg).digest() - digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2])) - # finally, use the b64-encoded folded digest as ID part of the unique ID: - digest = base64.urlsafe_b64encode(digest) - - return '%s' % (digest) + def new_edi_token(self,cr,uid,ids,browse_records,database_id,context=None): """Generate new unique tokent value to uniquely identify each edi_document""" - db_uuid = self.safe_unique_id(browse_records, database_id) + db_uuid = safe_unique_id(browse_records, database_id) edi_token = hashlib.sha256('%s-%s-%s' % (time.time(),db_uuid,time.time())).hexdigest() @@ -88,7 +90,7 @@ class ir_edi_document(osv.osv): raise osv.except_osv(_('Error !'), _('Desired EDI Document does not Exist')) - return True + return doc_string def load_edi(self,cr,uid,ids,edi_list_of_dicts,context=None): @@ -147,34 +149,91 @@ class edi(object): specific behavior, based on the primitives provided by this superclass.""" - def edi_metadata(self, cr, uid, browse_rec_list,model=None, context=None): + def edi_metadata(self, cr, uid, ids, browse_rec_list, context=None): """Return a list representing the boilerplate EDI structure for exporting the record in the given browse_rec_list, including the metadata fields""" # generic implementation! - if model is not None: - self.edi_document['__model'] = model + data_object = self.pool.get('ir.model.data') + if self.edi_document['__model'] is not None: + model_ids = data_object.search(cr,uid,[('model','=',self.edi_document['__model'])],context=context) + for data in data_object.browse(cr,uid,model_ids): + self.edi_document['__module'] = data.module + if data.name is not None: + self.edi_document['__id'] = context['active_id']+':'+data.name + self.edi_document['__last_updata'] = context['__last_update'].values()[0] return True - def edi_m2o(self, browse_rec, context=None): + def edi_m2o(self, cr, uid, ids, browse_rec,model=None,context=None): """Return a list representing a M2O EDI value for the given browse_record.""" # generic implementation! - return + res = [] + data_object = self.pool.get('ir.model.data') + uu_id = data_model.search(cr,uid,[('model','=',model)]) + for xml in data_model.browse(cr,uid,uu_id): + if xml: + xml_ID = xml.name + else: + xml_ID = None + for data in browse_rec: + if data.name is not None: + if xml_ID is not None: + res.append(safe_unique_id(model,data.id)+':'+xml_ID) + else: + res.append(safe_unique_id(model,data.id)+':'+model+'-'+safe_unique_id(model,data.id)) + res.append(data.name) + return res - def edi_o2m(self, browse_rec_list, context=None): + def edi_o2m(self, cr, uid, ids, browse_rec_list, model=None, context=None): """Return a list representing a O2M EDI value for the browse_records from the given browse_record_list.""" # generic implementation! - return + res = [] + init_data = {} + data_object = self.pool.get('ir.model.data') + uu_id = data_model.search(cr,uid,[('model','=',model)]) + for xml in data_model.browse(cr,uid,uu_id): + if xml: + xml_ID = xml.name + else: + xml_ID = None + data_model = self.pool.get(model) + for record in browse_rec_list: + if xml_ID is not None: + init_data['__id'] = safe_unique_id(model,record.id)+':'+xml_ID + else: + init_data['__id'] = safe_unique_id(model,record.id)+':'+model+'-'+safe_unique_id(model,record.id) + init_data['__last_update'] =record.write_date + init_data.update(datamodel.read(cr,uid,[record],[],context=context)[0]) + res.append(init_data) + return res - def edi_m2m(self, browse_rec_list, context=None): + def edi_m2m(self, cr, uid, ids, browse_rec_list,model=None, context=None): """Return a list representing a M2M EDI value for the browse_records from the given browse_record_list.""" # generic implementation! - return + res = [] + init_data = [] + data_object = self.pool.get('ir.model.data') + uu_id = data_model.search(cr,uid,[('model','=',model)]) + for xml in data_model.browse(cr,uid,uu_id): + if xml: + xml_ID = xml.name + else: + xml_ID = None + data_model = self.pool.get(model) + for record in browse_rec_list: + if xml_ID is not None: + init_data.append(safe_unique_id(model,record.id)+':'+xml_ID) + else: + init_data.append(safe_unique_id(model,record.id)+':'+model+'-'+safe_unique_id(model,record.id)) + if record.name is not None: + init_data.append('name:'+record.name) + res.append(init_data) + return res def edi_export(self, cr, uid, ids, edi_struct=None, context=None): """Returns a list of dicts representing an edi.document containing the @@ -199,40 +258,32 @@ class edi(object): self.edi_document = { - 'model':'', - 'module':'', - 'id':'', - 'last_update':'', - 'version':'', - 'attachments':'', + '__model':'', + '__module':'', + '__id':'', + '__last_update':'', + '__version':'', + '__attachments':'', } - if 'model' not in edi_struct.keys(): - return False - data_object = self.pool.get('ir.model.data') - - fields_object = self.pool.get('ir.model.fields') + self.edi_document['__model'] = self._name - for field in self.edi_document.keys(): - - if field in edi_struct.keys(): - if field == 'model': - model_name = edi_struct['model'] - record_ids = data_object.search(cr,uid,[('model','=',model_name)]) - for fname in data_object.browse(cr,uid,record_ids): - if fname.name: - xml_ID = fname.name + + fields_object = self.pool.get('ir.model.fields') + model_object = self.pool.get(self.edi_document['__model']) + edi_metadata(context=context) + for field in self.edi_struct.keys(): + f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',self.edi_document['__model'])],context=context) + for fname in fields_object.browse(cr,uid,f_ids): + if fname.ttype == 'many2one': - else: - - f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',model_name)],context=context) - for fname in fields_object.browse(cr,uid,f_ids): - if fname.ttype == 'many2one': - self.edi_document[field] = self.edi_m2o(edi_struct[field]) - elif fname.ttype == 'one2many': - self.edi_ducument[field] = self.edi_o2m(edi_struct[field]) - elif fname.ttype == 'many2many': - self.edi_document[field] = self.edi_m2m(edi_struct[field]) + self.edi_document[field] = self.edi_m2o(edi_struct[field],fname.relation) + elif fname.ttype == 'one2many': + self.edi_ducument[field] = self.edi_o2m(edi_struct[field],fname.relation) + elif fname.ttype == 'many2many': + self.edi_document[field] = self.edi_m2m(edi_struct[field],fname.relation) + else: + self.edi_document[field] = model_object.read(cr,uid,[context['active_id']],[field],context=context) return self.edi_document def edi_import(self, cr, uid, edi_document, context=None): @@ -240,4 +291,6 @@ class edi(object): generic algorithm. """ # generic implementation! + for field in edi_document.keys(): + pass From 4deb609685c4fa002d13bb102fb141b09b4f6782 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Wed, 1 Jun 2011 19:03:10 +0530 Subject: [PATCH 05/56] [EDI]: working on edi import bzr revid: aar@tinyerp.com-20110601133310-nklnhlymx8u38nsg --- openerp/addons/base/ir/ir_edi.py | 81 ++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 9d66d4ed866..a87453fd277 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -38,7 +38,7 @@ def safe_unique_id(model, database_id): # finally, use the b64-encoded folded digest as ID part of the unique ID: digest = base64.urlsafe_b64encode(digest) - return '%s' % (digest) + return '%s-%s' % (model,digest) class ir_edi_document(osv.osv): _name = 'ir.edi.document' @@ -51,7 +51,7 @@ class ir_edi_document(osv.osv): - def new_edi_token(self,cr,uid,ids,browse_records,database_id,context=None): + def new_edi_token(self,browse_records,database_id): """Generate new unique tokent value to uniquely identify each edi_document""" @@ -71,15 +71,14 @@ class ir_edi_document(osv.osv): def generate_edi(self,browse_record_list): """ Generate the list of dictionaries using edi_export method of edi class """ - - edi = edi() + edi_list = [] for record in browse_record_list: - edi_struct = edi.edi_export(record) + edi_struct = record.edi_export(record) edi_list.append(edi_struct) return self.serialize(edi_list) - def get_document(self,cr,uid,ids,edi_token,context=None): + def get_document(self,cr,uid,edi_token,context=None): """Get the edi document from database using given edi token """ @@ -96,21 +95,21 @@ class ir_edi_document(osv.osv): """loads the values from list of dictionaries to the corresponding OpenERP records using the edi_import method of edi class""" - edi = edi() + edi_list = [] for record in edi_list_of_dicts: - edi_record = edi.edi_import(record) + edi_record = record.edi_import(record) return True - def deserialize(self,edi_document_string): + def deserialize(self,cr,uid,edi_document_string,context=None): """ Deserialized the edi document string""" deserialize_string = json.loads(edi_document_string) - return self.load_edi(deserialize_string) + return self.load_edi(cr,uid,deserialize_string,context=context) - def export_doc(self, cr, uid, ids,browse_records,database_id,context=None): + def export_doc(self, cr, uid, browse_records, database_id, context=None): """the method handles the flow of the edi document generation and store it in the database and return the edi_token of the particular document""" @@ -129,10 +128,10 @@ class ir_edi_document(osv.osv): edi_loads method """ if edi_document is not None: - self.deserialize(edi_document) + self.deserialize(cr,uid,edi_document,context=context) elif edi_url is not None: edi_document = urllib2.urlopen(edi_url).read() - self.deserialize(edi_document) + self.deserialize(cr,uid,edi_document,context=context) return True @@ -149,7 +148,7 @@ class edi(object): specific behavior, based on the primitives provided by this superclass.""" - def edi_metadata(self, cr, uid, ids, browse_rec_list, context=None): + def edi_metadata(self, cr, uid,browse_rec_list, context=None): """Return a list representing the boilerplate EDI structure for exporting the record in the given browse_rec_list, including the metadata fields""" @@ -162,14 +161,18 @@ class edi(object): self.edi_document['__module'] = data.module if data.name is not None: self.edi_document['__id'] = context['active_id']+':'+data.name + else: + self.edi_document['__id'] = context['active_id']+':'+safe_unique_id(data.model,) self.edi_document['__last_updata'] = context['__last_update'].values()[0] return True - def edi_m2o(self, cr, uid, ids, browse_rec,model=None,context=None): + def edi_m2o(self, cr, uid,browse_rec): """Return a list representing a M2O EDI value for the given browse_record.""" # generic implementation! + + model = browse_rec._name res = [] data_object = self.pool.get('ir.model.data') uu_id = data_model.search(cr,uid,[('model','=',model)]) @@ -183,23 +186,26 @@ class edi(object): if xml_ID is not None: res.append(safe_unique_id(model,data.id)+':'+xml_ID) else: - res.append(safe_unique_id(model,data.id)+':'+model+'-'+safe_unique_id(model,data.id)) + res.append(safe_unique_id(model,data.id)+':'+safe_unique_id(model,data.id)) res.append(data.name) return res - def edi_o2m(self, cr, uid, ids, browse_rec_list, model=None, context=None): + def edi_o2m(self, cr, uid,line = None,browse_rec_list, context=None): """Return a list representing a O2M EDI value for the browse_records from the given browse_record_list.""" # generic implementation! res = [] init_data = {} + model = browse_rec_list._name data_object = self.pool.get('ir.model.data') uu_id = data_model.search(cr,uid,[('model','=',model)]) + for xml in data_model.browse(cr,uid,uu_id): if xml: xml_ID = xml.name else: xml_ID = None + data_model = self.pool.get(model) for record in browse_rec_list: if xml_ID is not None: @@ -207,14 +213,16 @@ class edi(object): else: init_data['__id'] = safe_unique_id(model,record.id)+':'+model+'-'+safe_unique_id(model,record.id) init_data['__last_update'] =record.write_date - init_data.update(datamodel.read(cr,uid,[record],[],context=context)[0]) + init_data.update(self.determine_type(cr,uid,ids,line,record,context=context)) + res.append(init_data) return res - def edi_m2m(self, cr, uid, ids, browse_rec_list,model=None, context=None): + def edi_m2m(self, cr, uid, line = None, browse_rec_list, context=None): """Return a list representing a M2M EDI value for the browse_records from the given browse_record_list.""" # generic implementation! + model = browse_rec_list[0]._name res = [] init_data = [] data_object = self.pool.get('ir.model.data') @@ -271,20 +279,43 @@ class edi(object): fields_object = self.pool.get('ir.model.fields') model_object = self.pool.get(self.edi_document['__model']) - edi_metadata(context=context) - for field in self.edi_struct.keys(): + edi_metadata(edi_struct,context=context) + record = model_object.read(cr,uid,ids,[],context=context) + for field in edi_struct.keys(): f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',self.edi_document['__model'])],context=context) + for fname in fields_object.browse(cr,uid,f_ids): if fname.ttype == 'many2one': - self.edi_document[field] = self.edi_m2o(edi_struct[field],fname.relation) + self.edi_document[field] = self.edi_m2o(cr, uid, browse_rec = record[0][field]) elif fname.ttype == 'one2many': - self.edi_ducument[field] = self.edi_o2m(edi_struct[field],fname.relation) + + self.edi_ducument[field] = self.edi_o2m(cr,uid,line = edi_struct[field],browse_rec_list = record[0][field]) elif fname.ttype == 'many2many': - self.edi_document[field] = self.edi_m2m(edi_struct[field],fname.relation) + self.edi_document[field] = self.edi_m2m(cr,uid,line = edi_struct[field],browse_rec_list = record[0][field]) else: - self.edi_document[field] = model_object.read(cr,uid,[context['active_id']],[field],context=context) + self.edi_document[field] = record[0][field] + + return self.edi_document + def determine_type(self,cr,uid,ids,record_line,browse_record=None,context=None): + model = browse_record._name + fields_object = self.pool.get('ir.model.fields') + obj = self.pool.get(model) + field_data = obj.read(cr,uid,browse_record.id,record_line.keys())[0] + for field in field_data.keys(): + field_ids = field_object.search(cr,uid,[field]) + for field_type in field_object.browse(cr,uid,field_ids): + if field_type.ttype == 'many2one': + record_line.update(field,self.edi_m2o(cr,uid,browse_rec = field_data[field])) + elif field_type.ttype == 'one2many': + pass + elif field_type.ttype == 'many2many': + pass + else: + record_line.update(field,field_data[field]) + return record_line + def edi_import(self, cr, uid, edi_document, context=None): """Imports a list of dicts representing an edi.document, using the From 1e3ae46ec7a48a80b8625ae18aa654c936a8876c Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Thu, 2 Jun 2011 19:12:18 +0530 Subject: [PATCH 06/56] [EDI]: developing edi_import method bzr revid: aar@tinyerp.com-20110602134218-32aolwbdbwkp63qp --- openerp/addons/base/ir/ir_edi.py | 78 ++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index a87453fd277..d5146b4c655 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -240,7 +240,9 @@ class edi(object): init_data.append(safe_unique_id(model,record.id)+':'+model+'-'+safe_unique_id(model,record.id)) if record.name is not None: init_data.append('name:'+record.name) - res.append(init_data) + res.append(init_data) + + # blob: return res def edi_export(self, cr, uid, ids, edi_struct=None, context=None): @@ -279,7 +281,7 @@ class edi(object): fields_object = self.pool.get('ir.model.fields') model_object = self.pool.get(self.edi_document['__model']) - edi_metadata(edi_struct,context=context) + edi_metadata(cr, uid, edi_struct, context=context) record = model_object.read(cr,uid,ids,[],context=context) for field in edi_struct.keys(): f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',self.edi_document['__model'])],context=context) @@ -322,6 +324,76 @@ class edi(object): generic algorithm. """ # generic implementation! + new = True + resource = {} + model = edi_document['__model'] + model_object = self.pool.get(model) + data_model = self.pool.get('ir.model.data') + field_model = self.pool.get('ir.model.fields') for field in edi_document.keys(): + if field == '__id': + db_id , xml_ID = edi_document[field].split(':') + self.data_ids = data_model.search(cr,uid,[('name','=',xml_ID)]) - pass + + f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',model)],context=context) + for fname in fields_object.browse(cr,uid,f_ids): + + if fname.ttype == 'many2one': + field_ids,field_xml = edi_document[field][0].split(':') + field_ids = int(field_ids) + model_ids = data_model.search(cr,uid,[('name','=',field_xml)]) + if not len(model_ids): + obj = self.pool.get(fname.relation) + model1_ids = obj.search(cr,uid,[('name','=',edi_document[field][1])]) + if len(model1_ids) == 1: + resourse[field] = data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':model1_ids})[0] + + elif not len(model1_ids) or len(model1_ids) > 1: + edi_document[field][1] = obj.create(cr,uid,{'name':edi_document[field][1]}) + resource[field] = data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':edi_document[field][0]})[0] + else: + resource[field] = model_ids[0] + + elif fname.ttype == 'one2many': + for records in edi_document[field]: + res = {} + field_ids,field_xml = records['__id'].split(':') + field_ids = int(field_ids) + model3_ids = data_model.search(cr,uid,[('name','=',field_xml)]) + for fields in records.keys(): + if fields != '__id' or fields != '__last_update': + res[fields] = records[fields] + if not len(model3_ids): + resource[field][records] = (4,self.edi_import(cr,uid,res,context = context)[0]) + else: + resource[field][records] = (4,model3_ids[0])obj = self.pool.get(model) + record_ids = obj.create(cr,uid,resource,context=context) + return record_ids + + elif fname.ttype == 'many2many': + for records in edi_document[field]: + field_ids,field_xml = records[0].split(':') + field_ids = int(field_ids) + model4_ids = data_model.search(cr,uid,[('name','=',field_xml)]) + if not len(model4_ids): + obj = self.pool.get(fname.relation) + name,value = records[1].split(':') + model5_ids = obj.search(cr,uid,[(name,'=',value)]) + if len(model5_ids) == 1: + resource[field][records] = data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':model5_ids})[0] + elif not len(model5_ids) or len(model5_ids) > 1: + edi_document[field][records][1] = obj.create(cr,uid,{'name':edi_document[field][1]}) + resource[field] = (4,data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':edi_document[field][records][1]})[0]) + else: + resource[field] = (4,model4_ids[0]) + else: + resource[field] = edi_document[field] + if len(self.data_ids): + obj = self.pool.get(model) + record_ids = obj.write(cr,uid,resource,context=context) + return record_ids + else: + obj = self.pool.get(model) + record_ids = obj.create(cr,uid,resource,context=context) + return record_ids From f1ce7642fbf5edcb1cb392f4a7a1feb0a3725888 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 3 Jun 2011 12:59:50 +0530 Subject: [PATCH 07/56] [REVIEW] edi: optimize code bzr revid: hmo@tinyerp.com-20110603072950-441p4kd4vyvzpo7a --- openerp/addons/base/ir/ir_edi.py | 344 +++++++++++++++++-------------- 1 file changed, 190 insertions(+), 154 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index d4d3692203d..2a0c9282de9 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -27,10 +27,24 @@ import base64 import urllib2 def safe_unique_id(model, database_id): - """Generate a unique string to represent a (model,database id) pair without being too long, without revealing the database id, and with a very low probability of collisions. + + Each EDI record and each relationship value are represented using a unique + database identifier. These database identifiers include the database unique + ID, as a way to uniquely refer to any record within any OpenERP instance, + without conflict. + + For OpenERP records that have an existing "XML ID" (i.e. an entry in + ir.model.data), the EDI unique identifier for this record will be made of + "%s:%s" % (the database's UUID, the XML ID). The database's UUID MUST + NOT contain a colon characters (this is guaranteed by the UUID algorithm). + + For OpenERP records that have no existing "XML ID", a new one should be + created during the EDI export. It is recommended that the generated XML ID + contains a readable reference to the record model, plus a unique value that + hides the database ID. """ msg = "%s-%s-%s" % (model, time.time(), database_id) digest = hashlib.sha1(msg).digest() @@ -42,10 +56,10 @@ def safe_unique_id(model, database_id): class ir_edi_document(osv.osv): _name = 'ir.edi.document' - _description = 'ir_edi_document' + _description = 'To represent the EDI export of any OpenERP record, it could be an invoice, a project task, etc. _columns = { - 'name': fields.char("EDI token", size = 128), - 'document': fields.text("Document") + 'name': fields.char("EDI token", size = 128, help="EDI Token is a unique identifier for the EDI document."), + 'document': fields.text("Document", help="hold the serialization of the EDI document.") } _sql_constraints = [ @@ -53,90 +67,115 @@ class ir_edi_document(osv.osv): ] - def new_edi_token(self,browse_records,database_id): - - """Generate new unique tokent value to uniquely identify each edi_document""" - - db_uuid = safe_unique_id(browse_records, database_id) - edi_token = hashlib.sha256('%s-%s-%s' % (time.time(),db_uuid,time.time())).hexdigest() - - + def new_edi_token(self, record): + """ + Return a new, random unique token to identify an edi.document + :param record: It's a object of browse_record of any model + """ + db_uuid = safe_unique_id(record._name, record.id) + edi_token = hashlib.sha256('%s-%s-%s' % (time.time(), db_uuid, time.time())).hexdigest() return edi_token - def serialize(self,edi_list_of_dicts): - - """Serialize the list of dictionaries using json dumps method""" - - serialized_list = json.dumps(edi_list_of_dicts) + def serialize(self, edi_documents): + """Serialize the list of dictionaries using json dumps method + perform a JSON serialization of a list of dicts prepared by generate_edi() and return a UTF-8 encoded string that could be passed to deserialize() + :param edi_dicts: it's list of edi_dict + """ + serialized_list = json.dumps(edi_dicts) return serialized_list - def generate_edi(self,browse_record_list): - - """ Generate the list of dictionaries using edi_export method of edi class """ - + def generate_edi(self, cr, uid, records, context=None): + """ + Generate the list of dictionaries using edi_export method of edi class + :param records: it's a object of browse_record_list of any model + """ edi_list = [] - for record in browse_record_list: - edi_struct = record.edi_export(record) + for record in record_list: + edi_struct = record.edi_export(cr, uid, record, context=context) edi_list.append(edi_struct) return self.serialize(edi_list) - def get_document(self,cr,uid,edi_token,context=None): + def get_document(self, cr, uid, edi_token, context=None): + """ + Get the edi document from database using given edi token + returns the string serialization that is in the database (column: document) for the given edi_token or raise. + """ - """Get the edi document from database using given edi token """ - - records = self.search(cr,uid,[('name','=',edi_token)],context=context) + records = self.name_search(cr, uid, edi_token, context=context) if records: - doc_string = self.read(cr,uid,records,['document'],context=context) + edi = self.browse(cr, uid, records[0], context=context) + return edi.document else: raise osv.except_osv(_('Error !'), _('Desired EDI Document does not Exist')) - return doc_string + - def load_edi(self,cr,uid,ids,edi_list_of_dicts,context=None): - - """loads the values from list of dictionaries to the corresponding OpenERP records - using the edi_import method of edi class""" - - edi_list = [] - for record in edi_list_of_dicts: - edi_record = record.edi_import(record) - - - return True - - def deserialize(self,cr,uid,edi_document_string,context=None): - """ Deserialized the edi document string""" - - deserialize_string = json.loads(edi_document_string) - return self.load_edi(cr,uid,deserialize_string,context=context) - - def export_doc(self, cr, uid, browse_records, database_id, context=None): - """the method handles the flow of the edi document generation and store it in - the database and return the edi_token of the particular document""" - - edi_document = { - 'name': self.new_edi_token(browse_records,database_id), - 'document': self.generate_edi(browse_records) - } - - unique_id = self.create(cr,uid,edi_document,context=context) - return edi_document['name'] - - def import_doc(self, cr, uid, ids, edi_document=None,edi_url=None,context=None): - - """the method handles the flow of importing particular edi document and - updates the database values on the basis of the edi document using - edi_loads method """ - - if edi_document is not None: - self.deserialize(cr,uid,edi_document,context=context) - elif edi_url is not None: - edi_document = urllib2.urlopen(edi_url).read() - self.deserialize(cr,uid,edi_document,context=context) + def load_edi(self, cr, uid, edi_documents, context=None): + """ + loads the values from list of dictionaries to the corresponding OpenERP records + using the edi_import method of edi class + For each edi record (dict) in the list, call the corresponding osv.edi_import() method, based on the __model attribute (see section 2.4 of for spec of + osv.edi_import) + :param edi_dicts: list of edi_dict + """ + + for edi_document in edi_documents: + model = edi_document.get('__model') + assert model, _('model should be provided in EDI Dict') + model_obj = self.pool.get(model) + model_obj.edi_import(cr, uid, edi_document, context=context) return True + def deserialize(self, edi_document_string): + """ Deserialized the edi document string + perform JSON deserialization from an edi document string, and returns a list of dicts + """ + edi_document = json.loads(edi_document_string) + return edi_document + + def export(self, cr, uid, records, context=None): + """ + The method handles the flow of the edi document generation and store it in + the database and return the edi_token of the particular document + Steps: + * call generate_edi() to get a serialization and new_edi_token() to get a unique ID + * serialize the list returned by generate_edi() using serialize(), and save it in database with unique ID. + * return the unique ID + + : param records: list of browse_record of any model + """ + exported_ids = [] + for record in records: + document = self.generate_edi(cr, uid, [record], context) + token = self.new_edi_token(record) + self.create(cr, uid, { + 'name': token, + 'document': document + }, context=context) + + exported_ids.append(token) + return exported_ids + + def import(self, cr, uid, edi_document=None, edi_url=None, context=None): + """ + The method handles the flow of importing particular edi document and + updates the database values on the basis of the edi document using + edi_loads method + + * N: a serialized edi.document or the URL to download a serialized document + * If a URL is provided, download it first to get the document + * Calls deserialize() to get the resulting list of dicts from the document + * Call load_edi() with the list of dicts, to create or update the corresponding OpenERP records based on the edi.document. + """ + + if edi_url and not edi_document: + edi_document = urllib2.urlopen(edi_url).read() + assert edi_document, _('EDI Document should be provided') + edi_documents = self.deserialize(edi_document) + return self.load_edi(cr, uid, edi_documents, context=context) + ir_edi_document() @@ -150,102 +189,99 @@ class edi(object): specific behavior, based on the primitives provided by this superclass.""" - def edi_metadata(self, cr, uid,browse_rec_list, context=None): + def edi_metadata(self, cr, uid, records, context=None): """Return a list representing the boilerplate EDI structure for exporting the record in the given browse_rec_list, including - the metadata fields""" - # generic implementation! - data_object = self.pool.get('ir.model.data') + the metadata fields - if self.edi_document['__model'] is not None: - model_ids = data_object.search(cr,uid,[('model','=',self.edi_document['__model'])],context=context) - for data in data_object.browse(cr,uid,model_ids): - self.edi_document['__module'] = data.module - if data.name is not None: - self.edi_document['__id'] = context['active_id']+':'+data.name - else: - self.edi_document['__id'] = context['active_id']+':'+safe_unique_id(data.model,) - self.edi_document['__last_updata'] = context['__last_update'].values()[0] + The metadata fields MUST always include: + - __model': the OpenERP model name + - __module': the OpenERP module containing the model + - __id': the unique (cross-DB) identifier for this record + - __last_update': last update date of record, ISO date string in UTC + - __version': a list of components for the version + - __attachments': a list (possibly empty) of dicts describing the files attached to this record. + """ + # generic implementation! + attachment_object = self.pool.get('ir.attachment') + edi_dict_list = [] + for record in records: + attachment_ids = attachment_object.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)]) + attachment_dict_list = [] + for attachment in attachment_object.browse(cr, uid, attachment_ids, context=context): + attachment_dict_list.append({ + 'name' : attachment.name, + 'content': base64.encodestring(attachment.datas), + 'file_name': attachment.datas_fname, + }) + edi_dict = { + '__model' : record._name, + '__module' : record._module, + '__id': record.id, + '__last_update': record.write_date, #TODO: convert into UTC + '__version': [], # ? + '__attachments': attachment_dict_list + } + edi_dict_list.append(edi_dict) - return True + return edi_dict_list - def edi_m2o(self, cr, uid,browse_rec): + def edi_m2o(self, cr, uid, record, context=None): """Return a list representing a M2O EDI value for - the given browse_record.""" + the given browse_record. + M2O are passed as pair (ID, Name) + Exmaple: ['db-uuid:xml-id', 'Partner Name'] + """ # generic implementation! - - model = browse_rec._name - res = [] data_object = self.pool.get('ir.model.data') - uu_id = data_model.search(cr,uid,[('model','=',model)]) - for xml in data_model.browse(cr,uid,uu_id): - if xml: - xml_ID = xml.name - else: - xml_ID = None - for data in browse_rec: - if data.name is not None: - if xml_ID is not None: - res.append(safe_unique_id(model,data.id)+':'+xml_ID) - else: - res.append(safe_unique_id(model,data.id)+':'+safe_unique_id(model,data.id)) - res.append(data.name) - return res + db_uuid = safe_unique_id(record._name, record.id) + xml_ids = data_object.search(cr, uid, [('model','=',record._name),('res_id','=',record.id)]) + if xml_ids: + xml_id = data_object.browse(cr, uid, xml_ids[0], context=context) + xml_id = xml_id.name + db_uuid = '%s:%s' %(db_uuid, xml_id) + name = record.name + return [db_uuid, name] + - def edi_o2m(self, cr, uid,line = None,browse_rec_list, context=None): + def edi_o2m(self, cr, uid, records, context=None): """Return a list representing a O2M EDI value for - the browse_records from the given browse_record_list.""" - # generic implementation! - res = [] - init_data = {} - model = browse_rec_list._name - data_object = self.pool.get('ir.model.data') - uu_id = data_model.search(cr,uid,[('model','=',model)]) - - for xml in data_model.browse(cr,uid,uu_id): - if xml: - xml_ID = xml.name - else: - xml_ID = None - - data_model = self.pool.get(model) - for record in browse_rec_list: - if xml_ID is not None: - init_data['__id'] = safe_unique_id(model,record.id)+':'+xml_ID - else: - init_data['__id'] = safe_unique_id(model,record.id)+':'+model+'-'+safe_unique_id(model,record.id) - init_data['__last_update'] =record.write_date - init_data.update(self.determine_type(cr,uid,ids,line,record,context=context)) - - res.append(init_data) - return res + the browse_records from the given browse_record_list. - def edi_m2m(self, cr, uid, line = None, browse_rec_list, context=None): - """Return a list representing a M2M EDI value for - the browse_records from the given browse_record_list.""" + Example: + [ # O2M fields would be a list of dicts, with their + { '__id': 'db-uuid:xml-id', # own __id. + '__last_update': 'iso date', # The update date, just in case... + 'name': 'some name', + ... + }], + """ # generic implementation! - model = browse_rec_list[0]._name - res = [] - init_data = [] - data_object = self.pool.get('ir.model.data') - uu_id = data_model.search(cr,uid,[('model','=',model)]) - for xml in data_model.browse(cr,uid,uu_id): - if xml: - xml_ID = xml.name - else: - xml_ID = None - data_model = self.pool.get(model) - for record in browse_rec_list: - if xml_ID is not None: - init_data.append(safe_unique_id(model,record.id)+':'+xml_ID) - else: - init_data.append(safe_unique_id(model,record.id)+':'+model+'-'+safe_unique_id(model,record.id)) - if record.name is not None: - init_data.append('name:'+record.name) - res.append(init_data) - - # blob: - return res + dict_list = [] + for record in records: + model_obj = self.pool.get(record._name) + dict_list += model_obj.edi_export(cr, uid, [record.id], context=context) + return dict_list + + + def edi_m2m(self, cr, uid, records, context=None): + """Return a list representing a M2M EDI value for + the browse_records from the given browse_record_list. + + Example: + 'related_tasks': [ # M2M fields would exported as a list of pairs, + ['db-uuid:xml-id1', # similar to a list of M2O values. + 'Task 01: bla bla'], + ['db-uuid:xml-id2', + 'Task 02: bla bla'] + ] + """ + # generic implementation! + dict_list = [] + for record in records: + dict_list.append(self.edi_o2m(cr, uid, record, context=None)) + + return dict_list def edi_export(self, cr, uid, ids, edi_struct=None, context=None): """Returns a list of dicts representing an edi.document containing the From ef7ee72f406efb58ea5c9863d4b3fa5d698f547a Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 3 Jun 2011 14:34:08 +0530 Subject: [PATCH 08/56] [REVIEW] edi: refactor code in import/export method bzr revid: hmo@tinyerp.com-20110603090408-fcumns7va18trdwx --- openerp/addons/base/ir/ir_edi.py | 235 +++++++++++++++---------------- 1 file changed, 112 insertions(+), 123 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 2a0c9282de9..c428bca7cb2 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -89,10 +89,8 @@ class ir_edi_document(osv.osv): Generate the list of dictionaries using edi_export method of edi class :param records: it's a object of browse_record_list of any model """ - edi_list = [] - for record in record_list: - edi_struct = record.edi_export(cr, uid, record, context=context) - edi_list.append(edi_struct) + + edi_list = self.edi_export(cr, uid, records, context=context) return self.serialize(edi_list) def get_document(self, cr, uid, edi_token, context=None): @@ -283,6 +281,8 @@ class edi(object): return dict_list + + def edi_export(self, cr, uid, ids, edi_struct=None, context=None): """Returns a list of dicts representing an edi.document containing the browse_records with ``ids``, using the generic algorithm. @@ -303,135 +303,124 @@ class edi(object): be included in the exported data. """ # generic implementation! - - self.edi_document = { - - '__model':'', - '__module':'', - '__id':'', - '__last_update':'', - '__version':'', - '__attachments':'', - - } - self.edi_document['__model'] = self._name + if context is None: + context = {} + _fields = self.fields_get(cr, uid, context=context) - fields_object = self.pool.get('ir.model.fields') - model_object = self.pool.get(self.edi_document['__model']) - edi_metadata(cr, uid, edi_struct, context=context) - record = model_object.read(cr,uid,ids,[],context=context) - for field in edi_struct.keys(): - f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',self.edi_document['__model'])],context=context) - - for fname in fields_object.browse(cr,uid,f_ids): - if fname.ttype == 'many2one': - - self.edi_document[field] = self.edi_m2o(cr, uid, browse_rec = record[0][field]) - elif fname.ttype == 'one2many': - - self.edi_ducument[field] = self.edi_o2m(cr,uid,line = edi_struct[field],browse_rec_list = record[0][field]) - elif fname.ttype == 'many2many': - self.edi_document[field] = self.edi_m2m(cr,uid,line = edi_struct[field],browse_rec_list = record[0][field]) + fields_to_export = edi_struct and edi_struct.keys() or _fields.key() + + edi_dict_list = [] + for row in self.browse(cr, uid, ids, fields_to_export, context): + edi_dict = {} + edi_dict.update(self.edi_metadata(cr, uid, [row.id], context=context)[0]) + for field in fields_to_export: + cols = self._fields[field] + if cols['type'] == 'many2one': + m2o_record = row.getattrs(field) + value = self.m2o(cr, uid, m2o_record, context=context) + elif cols['type'] == 'many2many': + m2m_records = row.getattrs(field) + value = self.m2m(cr, uid, m2m_records, context=context) + elif cols['type'] == 'one2many': + o2m_records = row.getattrs(field) + value = self.o2m(cr, uid, o2m_records, context=context) else: - self.edi_document[field] = record[0][field] + value = row.getattrs(field) + edi_dict[field] = value + edi_dict_list.append(edi_dict) + return edi_dict_list - - return self.edi_document - def determine_type(self,cr,uid,ids,record_line,browse_record=None,context=None): - model = browse_record._name - fields_object = self.pool.get('ir.model.fields') - obj = self.pool.get(model) - field_data = obj.read(cr,uid,browse_record.id,record_line.keys())[0] - for field in field_data.keys(): - field_ids = field_object.search(cr,uid,[field]) - for field_type in field_object.browse(cr,uid,field_ids): - if field_type.ttype == 'many2one': - record_line.update(field,self.edi_m2o(cr,uid,browse_rec = field_data[field])) - elif field_type.ttype == 'one2many': - pass - elif field_type.ttype == 'many2many': - pass - else: - record_line.update(field,field_data[field]) - return record_line def edi_import(self, cr, uid, edi_document, context=None): """Imports a list of dicts representing an edi.document, using the generic algorithm. + + All relationship fields are exported in a special way, and provide their own + unique identifier, so that we can avoid duplication of records when importing. + Note: for all ir.model.data entries, the "module" value to use for read/write + should always be "edi_import", and the "name" value should be the full + db_id provided in the EDI. + + 1: Many2One + M2O fields are always exported as a pair [db_id, name], where db_id + is in the form "db_uuid:xml_id", both being values that come directly + from the original database. + The import should behave like this: + a. Look in ir.model.data for a record that matches the db_id. + If found, replace the m2o value with the correct database ID and stop. + If not found, continue to next step. + b. Perform name_search(name) to look for a record that matches the + given m2o name. If only one record is found, create the missing + ir.model.data record to link it to the db_id, and the replace the m2o + value with the correct database ID, then stop. If zero result or + multiple results are found, go to next step. + c. Create the new record using the only field value that is known: the + name, and create the ir.model.data entry to map to it. + This should work for many models, and if not, the module should + provide a custom EDI import logic to care for it. + + 2: 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. + a. 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 + using a write value like (4,db_id). + b. If not found via db_id, create a new entry using the same method that + imports a full EDI record (recursive call!), grab the resulting db id, + and use it to connect to the parent via a write value like (4, db_id). + + 3: Many2Many + M2M fields are always exported as a list of pairs similar to M2O. + For each pair in the M2M: + a. Perform the same steps as for a Many2One (see 1.2.1.1) + b. After finding the database ID of the final record in the database, + connect it to the parent record via a write value like (4, db_id). """ # generic implementation! - new = True - resource = {} - model = edi_document['__model'] - model_object = self.pool.get(model) - data_model = self.pool.get('ir.model.data') - field_model = self.pool.get('ir.model.fields') + fields = edi_document.keys() + fields_to_import = [] + data_line = [] + _fields = self.fields_get(cr, uid, context=context) for field in edi_document.keys(): - if field == '__id': - db_id , xml_ID = edi_document[field].split(':') - self.data_ids = data_model.search(cr,uid,[('name','=',xml_ID)]) - - - f_ids = fields_object.search(cr,uid,[('name','=',field),('model','=',model)],context=context) - for fname in fields_object.browse(cr,uid,f_ids): - - if fname.ttype == 'many2one': - field_ids,field_xml = edi_document[field][0].split(':') - field_ids = int(field_ids) - model_ids = data_model.search(cr,uid,[('name','=',field_xml)]) - if not len(model_ids): - obj = self.pool.get(fname.relation) - model1_ids = obj.search(cr,uid,[('name','=',edi_document[field][1])]) - if len(model1_ids) == 1: - resourse[field] = data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':model1_ids})[0] - - elif not len(model1_ids) or len(model1_ids) > 1: - edi_document[field][1] = obj.create(cr,uid,{'name':edi_document[field][1]}) - resource[field] = data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':edi_document[field][0]})[0] - else: - resource[field] = model_ids[0] - - elif fname.ttype == 'one2many': - for records in edi_document[field]: - res = {} - field_ids,field_xml = records['__id'].split(':') - field_ids = int(field_ids) - model3_ids = data_model.search(cr,uid,[('name','=',field_xml)]) - for fields in records.keys(): - if fields != '__id' or fields != '__last_update': - res[fields] = records[fields] - if not len(model3_ids): - resource[field][records] = (4,self.edi_import(cr,uid,res,context = context)[0]) - else: - resource[field][records] = (4,model3_ids[0])obj = self.pool.get(model) - record_ids = obj.create(cr,uid,resource,context=context) - return record_ids - - elif fname.ttype == 'many2many': - for records in edi_document[field]: - field_ids,field_xml = records[0].split(':') - field_ids = int(field_ids) - model4_ids = data_model.search(cr,uid,[('name','=',field_xml)]) - if not len(model4_ids): - obj = self.pool.get(fname.relation) - name,value = records[1].split(':') - model5_ids = obj.search(cr,uid,[(name,'=',value)]) - if len(model5_ids) == 1: - resource[field][records] = data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':model5_ids})[0] - elif not len(model5_ids) or len(model5_ids) > 1: - edi_document[field][records][1] = obj.create(cr,uid,{'name':edi_document[field][1]}) - resource[field] = (4,data_model.create(cr,uid,{'module':'edi_import','name':field_xml,'res_id':edi_document[field][records][1]})[0]) - else: - resource[field] = (4,model4_ids[0]) + if not field.startwith('__'): + field_to_import.append(field) + edi_field_value = edi_document[field] + if _fields[field]['type'] == 'many2one': + #Look in ir.model.data for a record that matches the db_id. + #If found, replace the m2o value with the correct database ID and stop. + #If not found, continue to next step. + + #Perform name_search(name) to look for a record that matches the + #given m2o name. If only one record is found, create the missing + #ir.model.data record to link it to the db_id, and the replace the m2o + #value with the correct database ID, then stop. If zero result or + #multiple results are found, go to next step. + + #Create the new record using the only field value that is known: the + #name, and create the ir.model.data entry to map to it. + #This should work for many models, and if not, the module should + #provide a custom EDI import logic to care for it. + pass + elif _fields[field]['type'] == 'one2many': + #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 + #using a write value like (4,db_id). + + #If not found via db_id, create a new entry using the same method that + #imports a full EDI record (recursive call!), grab the resulting db id, + #and use it to connect to the parent via a write value like (4, db_id). + elif _fields[field]['type'] == 'many2many': + #Perform the same steps as for a Many2One (see 1.2.1.1) + + + + #After finding the database ID of the final record in the database, + #connect it to the parent record via a write value like (4, db_id). else: - resource[field] = edi_document[field] - if len(self.data_ids): - obj = self.pool.get(model) - record_ids = obj.write(cr,uid,resource,context=context) - return record_ids - else: - obj = self.pool.get(model) - record_ids = obj.create(cr,uid,resource,context=context) - return record_ids + data_line.append(edi_field_value) + + return self.import_data(cr, uid, field_to_import, [data_line], mode='init', current_module='edi_import', noupdate=False, context=context, filename=None) + From eaec4a25f23e4182bc5620f1a9dcef664f811861 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 3 Jun 2011 14:41:09 +0530 Subject: [PATCH 09/56] [REVIEW] edi: refactor code in generate_edi() bzr revid: hmo@tinyerp.com-20110603091109-e1gn7mr0gpcr88sy --- openerp/addons/base/ir/ir_edi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index c428bca7cb2..1fc9371b66c 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -90,7 +90,10 @@ class ir_edi_document(osv.osv): :param records: it's a object of browse_record_list of any model """ - edi_list = self.edi_export(cr, uid, records, context=context) + edi_list = [] + for record in records: + record_model_obj = self.pool.get(record._name) + edi_list += record_model_obj.edi_export(cr, uid, [record], context=context) return self.serialize(edi_list) def get_document(self, cr, uid, edi_token, context=None): From 332d385390a48cd82670182c6b76e92bbf35397e Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 3 Jun 2011 16:15:39 +0530 Subject: [PATCH 10/56] [REVIEW] edi: refactor code of edi_import bzr revid: hmo@tinyerp.com-20110603104539-nh0i45zv6zna2wau --- openerp/addons/base/ir/ir_edi.py | 96 ++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 1fc9371b66c..35b1b411a38 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -386,44 +386,92 @@ class edi(object): fields = edi_document.keys() fields_to_import = [] data_line = [] + model_data = self.pool.get('ir.model.data') _fields = self.fields_get(cr, uid, context=context) + current_module = 'edi_import' + values = {} for field in edi_document.keys(): if not field.startwith('__'): field_to_import.append(field) edi_field_value = edi_document[field] - if _fields[field]['type'] == 'many2one': - #Look in ir.model.data for a record that matches the db_id. - #If found, replace the m2o value with the correct database ID and stop. - #If not found, continue to next step. + if _fields[field]['type'] in ('many2one', 'many2many'): - #Perform name_search(name) to look for a record that matches the - #given m2o name. If only one record is found, create the missing - #ir.model.data record to link it to the db_id, and the replace the m2o - #value with the correct database ID, then stop. If zero result or - #multiple results are found, go to next step. + if _fields[field]['type'] == 'many2one': + edi_parent_documents = [edi_field_value] + else: + edi_parent_documents = edi_field_value - #Create the new record using the only field value that is known: the - #name, and create the ir.model.data entry to map to it. - #This should work for many models, and if not, the module should - #provide a custom EDI import logic to care for it. - pass + parent_lines = [] + + for edi_parent_document in edi_parent_documents: + #Look in ir.model.data for a record that matches the db_id. + #If found, replace the m2o value with the correct database ID and stop. + #If not found, continue to next step + + db_uuid, xml_id = tuple(edi_parent_document[0].split(':')) + data_ids = model_data.name_search(cr, uid, xml_id) + if data_ids: + d = model_data.browse(cr, uid, data_ids[0], context=context) + parent_lines.append(d.res_id) + + else: + #Perform name_search(name) to look for a record that matches the + #given m2o name. If only one record is found, create the missing + #ir.model.data record to link it to the db_id, and the replace the m2o + #value with the correct database ID, then stop. If zero result or + #multiple results are found, go to next step. + #Create the new record using the only field value that is known: the + #name, and create the ir.model.data entry to map to it. + + relation_object = self.get.pool(_fields[field]['relation']) + relation_ids = relation_object.name_search(cr, uid, edi_parent_document[1]) + if relation_ids and len(relation_ids) == 1: + relation_id = relation_ids[0] + else: + relation_id = relation_object.create(cr, uid, {'name': edi_parent_document[1]}, context=context) + model_data.create(cr, uid, { + 'name': xml_id, + 'model': relation_object._name, + #'module':relation_object._module, + 'res_id':relation_id, + },context=context) + + parent_lines.append(relation_id) + + if _fields[field]['type'] == 'many2one': + values[field] = parent_lines[0] + else: + many2many_ids = [] + for m2m_id in parent_lines: + many2many_ids.append((4,m2m_id)) + values[field] = many2many_ids + + elif _fields[field]['type'] == 'one2many': #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 #using a write value like (4,db_id). - #If not found via db_id, create a new entry using the same method that #imports a full EDI record (recursive call!), grab the resulting db id, #and use it to connect to the parent via a write value like (4, db_id). - elif _fields[field]['type'] == 'many2many': - #Perform the same steps as for a Many2One (see 1.2.1.1) - - - - #After finding the database ID of the final record in the database, - #connect it to the parent record via a write value like (4, db_id). + + relations = [] + relation_object = self.get.pool(_fields[field]['relation']) + for edi_relation_document in edi_field_value: + db_uuid, xml_id = tuple(edi_relation_document.get('__id').split(':')) + data_ids = model_data.name_search(cr, uid, xml_id) + if data_ids: + d = model_data.browse(cr, uid, data_ids[0], context=context) + relations.append(d.res_id) + else: + r = relation_object.edi_import(cr, uid, edi_relation_document, context=context) + relations.append(r) + one2many_ids = [] + for o2m_id in relations: + one2many_ids.append((4,o2m_id)) + values[field] = one2many_ids else: - data_line.append(edi_field_value) + values[field] = edi_field_value - return self.import_data(cr, uid, field_to_import, [data_line], mode='init', current_module='edi_import', noupdate=False, context=context, filename=None) + return model_data._update(cr, uid, self._name, current_module, values, context=context) From 023ce68e841d91ae3610bf50774f61b2be7de090 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Mon, 6 Jun 2011 11:22:57 +0530 Subject: [PATCH 11/56] [EDI]: worked on the local time to UTC time conversion bzr revid: aar@tinyerp.com-20110606055257-d33gwzaidwxufn64 --- openerp/addons/base/ir/ir_edi.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 4ed7b0a4d3a..2cb40c4dfa7 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -26,6 +26,7 @@ import time import base64 import urllib2 import release +import datetime def safe_unique_id(model, database_id): """Generate a unique string to represent a (model,database id) pair @@ -216,14 +217,21 @@ class edi(object): 'content': base64.encodestring(attachment.datas), 'file_name': attachment.datas_fname, }) + ''' Here we are obtaining the release version ''' version = [] for ver in release.version.split('.'): version.append(int(ver)) + ''' Here we are converting local time into UTC time''' + UTC_OFFSET_TIMEDELTA = datetime.datetime.utcnow() - datetime.datetime.now() + local_datetime = datetime.datetime.strptime(record.write_date, "%Y-%m-%d %H:%M:%S") + result_utc_datetime = local_datetime - UTC_OFFSET_TIMEDELTA + write_date = result_utc_datetime.strftime("%Y-%m-%d %H:%M:%S") + edi_dict = { '__model' : record._name, '__module' : record._module, '__id': record.id, - '__last_update': record.write_date, #TODO: convert into UTC + '__last_update': write_date, #TODO: convert into UTC '__version': version, '__attachments': attachment_dict_list } From ab62f7b7802571f56a56fce7389b777d6fee2583 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Tue, 21 Jun 2011 18:46:32 +0530 Subject: [PATCH 12/56] [EDI] revised edi class bzr revid: aar@tinyerp.com-20110621131632-rdwn6in74q5t1wkj --- openerp/addons/base/__openerp__.py | 3 +- openerp/addons/base/ir/ir_edi.py | 316 +++++++++++++++++------------ 2 files changed, 191 insertions(+), 128 deletions(-) diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index 75e0a8c56d0..b0c437352f9 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -71,7 +71,7 @@ 'res/partner/wizard/partner_clear_ids_view.xml', 'res/partner/wizard/partner_wizard_ean_check_view.xml', 'res/partner/partner_data.xml', - + #'test/test_edi_document.yml', 'res/ir_property_view.xml', 'security/base_security.xml', 'publisher_warranty/publisher_warranty_view.xml', @@ -96,6 +96,7 @@ 'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules. 'test/test_config_users.yml', 'test/test_ir_values.yml', + ], 'installable': True, 'active': True, diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 2cb40c4dfa7..fe8320491bf 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -20,14 +20,13 @@ ############################################################################## from osv import osv,fields -import json +import simplejson import hashlib import time import base64 import urllib2 -import release -import datetime - +import unicodedata +import openerp.release as release def safe_unique_id(model, database_id): """Generate a unique string to represent a (model,database id) pair without being too long, without revealing the database id, and @@ -58,7 +57,7 @@ def safe_unique_id(model, database_id): class ir_edi_document(osv.osv): _name = 'ir.edi.document' - _description = 'To represent the EDI export of any OpenERP record, it could be an invoice, a project task, etc.' + _description = 'To represent' _columns = { 'name': fields.char("EDI token", size = 128, help="EDI Token is a unique identifier for the EDI document."), 'document': fields.text("Document", help="hold the serialization of the EDI document.") @@ -83,7 +82,7 @@ class ir_edi_document(osv.osv): perform a JSON serialization of a list of dicts prepared by generate_edi() and return a UTF-8 encoded string that could be passed to deserialize() :param edi_dicts: it's list of edi_dict """ - serialized_list = json.dumps(edi_dicts) + serialized_list = simplejson.dumps(edi_documents) return serialized_list def generate_edi(self, cr, uid, records, context=None): @@ -95,7 +94,7 @@ class ir_edi_document(osv.osv): edi_list = [] for record in records: record_model_obj = self.pool.get(record._name) - edi_list += record_model_obj.edi_export(cr, uid, [record], context=context) + edi_list += record_model_obj.edi_export(cr, uid, [record],context=context) return self.serialize(edi_list) def get_document(self, cr, uid, edi_token, context=None): @@ -104,14 +103,12 @@ class ir_edi_document(osv.osv): returns the string serialization that is in the database (column: document) for the given edi_token or raise. """ - records = self.name_search(cr, uid, edi_token, context=context) + records = self.search(cr, uid, [('name','=',edi_token)]) if records: - edi = self.browse(cr, uid, records[0], context=context) - return edi.document - else: - raise osv.except_osv(_('Error !'), - _('Desired EDI Document does not Exist')) - + for edi in self.browse(cr, uid, records, context=context): + return edi.document + else: + pass def load_edi(self, cr, uid, edi_documents, context=None): @@ -135,10 +132,11 @@ class ir_edi_document(osv.osv): """ Deserialized the edi document string perform JSON deserialization from an edi document string, and returns a list of dicts """ - edi_document = json.loads(edi_document_string) + edi_document = simplejson.loads(edi_document_string) + return edi_document - def export(self, cr, uid, records, context=None): + def export_edi(self, cr, uid, records, context=None): """ The method handles the flow of the edi document generation and store it in the database and return the edi_token of the particular document @@ -161,7 +159,7 @@ class ir_edi_document(osv.osv): exported_ids.append(token) return exported_ids - def import(self, cr, uid, edi_document=None, edi_url=None, context=None): + def import_edi(self, cr, uid, edi_document=None, edi_url=None, context=None): """ The method handles the flow of importing particular edi document and updates the database values on the basis of the edi document using @@ -175,8 +173,10 @@ class ir_edi_document(osv.osv): if edi_url and not edi_document: edi_document = urllib2.urlopen(edi_url).read() - assert edi_document, _('EDI Document should be provided') - edi_documents = self.deserialize(edi_document) + #assert edi_document, _('EDI Document should be provided') + else: + + edi_documents = self.deserialize(edi_document) return self.load_edi(cr, uid, edi_documents, context=context) ir_edi_document() @@ -206,8 +206,11 @@ class edi(object): - __attachments': a list (possibly empty) of dicts describing the files attached to this record. """ # generic implementation! + model_data = self.pool.get('ir.model.data') + data_ids = [] attachment_object = self.pool.get('ir.attachment') edi_dict_list = [] + db_uuid = '' for record in records: attachment_ids = attachment_object.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)]) attachment_dict_list = [] @@ -217,22 +220,24 @@ class edi(object): 'content': base64.encodestring(attachment.datas), 'file_name': attachment.datas_fname, }) - ''' Here we are obtaining the release version ''' version = [] - for ver in release.version.split('.'): - version.append(int(ver)) - ''' Here we are converting local time into UTC time''' - UTC_OFFSET_TIMEDELTA = datetime.datetime.utcnow() - datetime.datetime.now() - local_datetime = datetime.datetime.strptime(record.write_date, "%Y-%m-%d %H:%M:%S") - result_utc_datetime = local_datetime - UTC_OFFSET_TIMEDELTA - write_date = result_utc_datetime.strftime("%Y-%m-%d %H:%M:%S") + + for ver in release.major_version.split('.'): + version.append(ver) + data_ids = model_data.search(cr, uid, [('res_id','=',record.id)]) + + if len(data_ids): + xml_obj = model_data.browse(cr,uid,data_ids[0]) + uuid = safe_unique_id(record._name,record.id) + db_uuid = '%s:%s' % (uuid,xml_obj.name) + edi_dict = { '__model' : record._name, '__module' : record._module, - '__id': record.id, - '__last_update': write_date, #TODO: convert into UTC - '__version': version, + '__id': db_uuid, + '__last_update': False, #record.write_date, #TODO: convert into UTC + '__version': version, # ? '__attachments': attachment_dict_list } edi_dict_list.append(edi_dict) @@ -249,10 +254,15 @@ class edi(object): data_object = self.pool.get('ir.model.data') db_uuid = safe_unique_id(record._name, record.id) xml_ids = data_object.search(cr, uid, [('model','=',record._name),('res_id','=',record.id)]) + if xml_ids: xml_id = data_object.browse(cr, uid, xml_ids[0], context=context) xml_id = xml_id.name - db_uuid = '%s:%s' %(db_uuid, xml_id) + else: + xml_id = record._name + db_uuid = '%s:%s' %(db_uuid, xml_id) + + name = record.name return [db_uuid, name] @@ -271,9 +281,12 @@ class edi(object): """ # generic implementation! dict_list = [] + for record in records: + model_obj = self.pool.get(record._name) - dict_list += model_obj.edi_export(cr, uid, [record.id], context=context) + dict_list += model_obj.edi_export(cr, uid, [record], context=context) + return dict_list @@ -291,14 +304,15 @@ class edi(object): """ # generic implementation! dict_list = [] + for record in records: - dict_list.append(self.edi_o2m(cr, uid, record, context=None)) - + dict_list.append(self.edi_o2m(cr, uid, [record], context=None)) + return dict_list - def edi_export(self, cr, uid, ids, edi_struct=None, context=None): + def edi_export(self, cr, uid, records, edi_struct=None, context=None): """Returns a list of dicts representing an edi.document containing the browse_records with ``ids``, using the generic algorithm. :param edi_struct: if provided, edi_struct should be a dictionary @@ -318,32 +332,39 @@ class edi(object): be included in the exported data. """ # generic implementation! + if context is None: context = {} _fields = self.fields_get(cr, uid, context=context) - fields_to_export = edi_struct and edi_struct.keys() or _fields.key() - + fields_to_export = edi_struct and edi_struct.keys() or _fields.keys() edi_dict_list = [] - for row in self.browse(cr, uid, ids, fields_to_export, context): + value = None + for row in records: edi_dict = {} - edi_dict.update(self.edi_metadata(cr, uid, [row.id], context=context)[0]) + edi_dict.update(self.edi_metadata(cr, uid, [row], context=context)[0]) + for field in fields_to_export: - cols = self._fields[field] - if cols['type'] == 'many2one': - m2o_record = row.getattrs(field) - value = self.m2o(cr, uid, m2o_record, context=context) - elif cols['type'] == 'many2many': - m2m_records = row.getattrs(field) - value = self.m2m(cr, uid, m2m_records, context=context) - elif cols['type'] == 'one2many': - o2m_records = row.getattrs(field) - value = self.o2m(cr, uid, o2m_records, context=context) + cols = _fields[field] + if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): + + pass else: - value = row.getattrs(field) + if cols['type'] == 'many2one': + m2o_record = getattr(row, field) + value = self.edi_m2o(cr, uid, m2o_record, context=context) + elif cols['type'] == 'many2many': + m2m_records = getattr(row, field) + value = self.edi_m2m(cr, uid, m2m_records, context=context) + elif cols['type'] == 'one2many': + o2m_records = getattr(row, field) + value = self.edi_o2m(cr, uid, o2m_records, context=context ) + else: + value = getattr(row, field) edi_dict[field] = value edi_dict_list.append(edi_dict) + return edi_dict_list @@ -395,6 +416,19 @@ class edi(object): connect it to the parent record via a write value like (4, db_id). """ # generic implementation! + + def convert(data): + if isinstance(data, unicode): + return str(data) + elif isinstance(data, dict): + return dict(map(convert, data.iteritems())) + elif isinstance(data, (list, tuple, set, frozenset)): + return type(data)(map(convert, data)) + else: + return data + #edi_document = convert(edi_document) + + fields = edi_document.keys() fields_to_import = [] data_line = [] @@ -403,87 +437,115 @@ class edi(object): current_module = 'edi_import' values = {} for field in edi_document.keys(): - if not field.startwith('__'): - field_to_import.append(field) + if field.startswith('__') and field == '__id': + db_uuid, xml_ids = tuple(edi_document[field].split(':')) + + elif not field.startswith('__'): + + + fields_to_import.append(field) edi_field_value = edi_document[field] - if _fields[field]['type'] in ('many2one', 'many2many'): - - if _fields[field]['type'] == 'many2one': - edi_parent_documents = [edi_field_value] - else: - edi_parent_documents = edi_field_value - - parent_lines = [] - - for edi_parent_document in edi_parent_documents: - #Look in ir.model.data for a record that matches the db_id. - #If found, replace the m2o value with the correct database ID and stop. - #If not found, continue to next step - - db_uuid, xml_id = tuple(edi_parent_document[0].split(':')) - data_ids = model_data.name_search(cr, uid, xml_id) - if data_ids: - d = model_data.browse(cr, uid, data_ids[0], context=context) - parent_lines.append(d.res_id) - - else: - #Perform name_search(name) to look for a record that matches the - #given m2o name. If only one record is found, create the missing - #ir.model.data record to link it to the db_id, and the replace the m2o - #value with the correct database ID, then stop. If zero result or - #multiple results are found, go to next step. - #Create the new record using the only field value that is known: the - #name, and create the ir.model.data entry to map to it. - - relation_object = self.get.pool(_fields[field]['relation']) - relation_ids = relation_object.name_search(cr, uid, edi_parent_document[1]) - if relation_ids and len(relation_ids) == 1: - relation_id = relation_ids[0] - else: - relation_id = relation_object.create(cr, uid, {'name': edi_parent_document[1]}, context=context) - model_data.create(cr, uid, { - 'name': xml_id, - 'model': relation_object._name, - #'module':relation_object._module, - 'res_id':relation_id, - },context=context) - - parent_lines.append(relation_id) - - if _fields[field]['type'] == 'many2one': - values[field] = parent_lines[0] - else: - many2many_ids = [] - for m2m_id in parent_lines: - many2many_ids.append((4,m2m_id)) - values[field] = many2many_ids - + if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): - elif _fields[field]['type'] == 'one2many': - #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 - #using a write value like (4,db_id). - #If not found via db_id, create a new entry using the same method that - #imports a full EDI record (recursive call!), grab the resulting db id, - #and use it to connect to the parent via a write value like (4, db_id). - - relations = [] - relation_object = self.get.pool(_fields[field]['relation']) - for edi_relation_document in edi_field_value: - db_uuid, xml_id = tuple(edi_relation_document.get('__id').split(':')) - data_ids = model_data.name_search(cr, uid, xml_id) - if data_ids: - d = model_data.browse(cr, uid, data_ids[0], context=context) - relations.append(d.res_id) + pass + else: + if _fields[field]['type'] in ('many2one', 'many2many'): + + if _fields[field]['type'] == 'many2one': + edi_parent_documents = [edi_field_value] + else: + edi_parent_documents = edi_field_value + + parent_lines = [] + + for edi_parent_document in edi_parent_documents: + #Look in ir.model.data for a record that matches the db_id. + #If found, replace the m2o value with the correct database ID and stop. + #If not found, continue to next step + if edi_parent_document[0].find(':') != -1 and edi_parent_document[1] != None: + db_uuid, xml_id = tuple(edi_parent_document[0].split(':')) + data_ids = model_data.name_search(cr, uid, xml_id) + if data_ids: + for data in model_data.browse(cr, uid, [data_ids[0][0]], context=context): + parent_lines.append(data.res_id) + print "..............................................",data.res_id + else: + #Perform name_search(name) to look for a record that matches the + #given m2o name. If only one record is found, create the missing + #ir.model.data record to link it to the db_id, and the replace the m2o + #value with the correct database ID, then stop. If zero result or + #multiple results are found, go to next step. + #Create the new record using the only field value that is known: the + #name, and create the ir.model.data entry to map to it. + + relation_object = self.pool.get(_fields[field]['relation']) + print "parent_document",edi_parent_document[1] + relation_ids = relation_object.search(cr, uid, [('name','=',edi_parent_document[1])]) + if relation_ids and len(relation_ids) == 1: + relation_id = relation_ids[0] + else: + relation_id = relation_object.create(cr, uid, {'name': edi_parent_document[1]}, context=context) + model_data.create(cr, uid, { + 'name': xml_id, + 'model': relation_object._name, + 'module':relation_object._module, + 'res_id':relation_id + }, context=context) + + parent_lines.append(relation_id) + + else: + pass + if len(parent_lines): + if _fields[field]['type'] == 'many2one': + values[field] = parent_lines[0] + + else: + many2many_ids = [] + for m2m_id in parent_lines: + many2many_ids.append((4,m2m_id)) + values[field] = many2many_ids + + + elif _fields[field]['type'] == 'one2many': + #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 + #using a write value like (4,db_id). + #If not found via db_id, create a new entry using the same method that + #imports a full EDI record (recursive call!), grab the resulting db id, + #and use it to connect to the parent via a write value like (4, db_id). + + relations = [] + relation_object = self.pool.get(_fields[field]['relation']) + for edi_relation_document in edi_field_value: + '''print "pppppppppppppppppppppppppppppppppppppppppp" + print edi_field_value + print ":::::::::::::::::::::::::::::::::::::::::::::" + print edi_relation_document + print edi_parent_document + print "ppppppppppppppppppppppppppppppppppp" + if edi_relation_document['__id'].find(':') != -1: + db_uuid, xml_id = tuple(edi_relation_document['__id'].split(':')) + print xml_id + data_ids = model_data.name_search(cr, uid, xml_id) + print "dddddddddddddddddddddddddddddddddddddddddddddd",data_ids + if data_ids: + for data in model_data.browse(cr,uid,[data_ids[0][0]]): + relations.append(data.res_id) + else:''' r = relation_object.edi_import(cr, uid, edi_relation_document, context=context) relations.append(r) - one2many_ids = [] - for o2m_id in relations: - one2many_ids.append((4,o2m_id)) - values[field] = one2many_ids - else: - values[field] = edi_field_value + print "relation id ssssssssssssssssssss",relations + one2many_ids = [] + for o2m_id in relations: + one2many_ids.append((4,o2m_id)) + values[field] = one2many_ids + else: + values[field] = edi_field_value + for val in values.keys(): + print '::::::::::::::::::::::',val,'::::::::::;;;;;;;;;;;;;;;;;;;;;;' + print values[val] return model_data._update(cr, uid, self._name, current_module, values, context=context) - +# vim: ts=4 sts=4 sw=4 si et \ No newline at end of file From e60caf7d51b5f86f3597d610708a42ba3eeadbc6 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Tue, 21 Jun 2011 18:49:27 +0530 Subject: [PATCH 13/56] [EDI] revised edi class bzr revid: aar@tinyerp.com-20110621131927-872hkmor321f1gdf --- openerp/addons/base/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index b0c437352f9..bccaf7f660b 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -71,7 +71,7 @@ 'res/partner/wizard/partner_clear_ids_view.xml', 'res/partner/wizard/partner_wizard_ean_check_view.xml', 'res/partner/partner_data.xml', - #'test/test_edi_document.yml', + 'test/test_edi_document.yml', 'res/ir_property_view.xml', 'security/base_security.xml', 'publisher_warranty/publisher_warranty_view.xml', From 395b953f47d1098732aab9999535a8e6bca88329 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Tue, 21 Jun 2011 18:51:33 +0530 Subject: [PATCH 14/56] [EDI] revised edi class bzr revid: aar@tinyerp.com-20110621132133-g24z7zi1ln16h06d --- openerp/addons/base/test/test_edi_documents.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 openerp/addons/base/test/test_edi_documents.yml diff --git a/openerp/addons/base/test/test_edi_documents.yml b/openerp/addons/base/test/test_edi_documents.yml new file mode 100644 index 00000000000..1b66caaf4dc --- /dev/null +++ b/openerp/addons/base/test/test_edi_documents.yml @@ -0,0 +1,16 @@ +- + Tesing of Export functionality +- + !python {model: ir.edi.document}: | + partner_obj = self.pool.get('res.partner') + partner_ids = partner_obj.search(cr, uid, []) + + if partner_ids: + partners = partner_obj.browse(cr, uid, partner_ids) + tokens = self.export_edi(cr, uid, partners) + print '>>>>>>>>>>',tokens + for token in tokens: + document = self.get_document(cr, uid, token, context=context) + a = self.import_edi(cr, uid, edi_document = document) + print '////////////',a + From 6d4b05e3d4840629fe04bffb75ef078174dfa65b Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Wed, 22 Jun 2011 14:41:23 +0530 Subject: [PATCH 15/56] [EDI] refactored code of edi class bzr revid: aar@tinyerp.com-20110622091123-6zal2su3vy8nv1nm --- openerp/addons/base/__openerp__.py | 2 +- openerp/addons/base/ir/ir_edi.py | 79 +++---------------- .../addons/base/test/test_edi_documents.yml | 4 +- 3 files changed, 15 insertions(+), 70 deletions(-) diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index bccaf7f660b..6406ec7b716 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -71,7 +71,6 @@ 'res/partner/wizard/partner_clear_ids_view.xml', 'res/partner/wizard/partner_wizard_ean_check_view.xml', 'res/partner/partner_data.xml', - 'test/test_edi_document.yml', 'res/ir_property_view.xml', 'security/base_security.xml', 'publisher_warranty/publisher_warranty_view.xml', @@ -96,6 +95,7 @@ 'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules. 'test/test_config_users.yml', 'test/test_ir_values.yml', + 'test/test_edi_documents.yml', ], 'installable': True, diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index fe8320491bf..fa41da161cc 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -20,12 +20,11 @@ ############################################################################## from osv import osv,fields -import simplejson import hashlib +import json import time import base64 import urllib2 -import unicodedata import openerp.release as release def safe_unique_id(model, database_id): """Generate a unique string to represent a (model,database id) pair @@ -82,7 +81,7 @@ class ir_edi_document(osv.osv): perform a JSON serialization of a list of dicts prepared by generate_edi() and return a UTF-8 encoded string that could be passed to deserialize() :param edi_dicts: it's list of edi_dict """ - serialized_list = simplejson.dumps(edi_documents) + serialized_list = json.dumps(edi_documents) return serialized_list def generate_edi(self, cr, uid, records, context=None): @@ -94,7 +93,7 @@ class ir_edi_document(osv.osv): edi_list = [] for record in records: record_model_obj = self.pool.get(record._name) - edi_list += record_model_obj.edi_export(cr, uid, [record],context=context) + edi_list += record_model_obj.edi_export(cr, uid, [record], context=context) return self.serialize(edi_list) def get_document(self, cr, uid, edi_token, context=None): @@ -103,13 +102,12 @@ class ir_edi_document(osv.osv): returns the string serialization that is in the database (column: document) for the given edi_token or raise. """ - records = self.search(cr, uid, [('name','=',edi_token)]) + records = self.name_search(cr, uid, edi_token) if records: - for edi in self.browse(cr, uid, records, context=context): - return edi.document + edi = self.browse(cr, uid, records, context=context) + return edi.document else: pass - def load_edi(self, cr, uid, edi_documents, context=None): """ @@ -132,7 +130,7 @@ class ir_edi_document(osv.osv): """ Deserialized the edi document string perform JSON deserialization from an edi document string, and returns a list of dicts """ - edi_document = simplejson.loads(edi_document_string) + edi_document = json.loads(edi_document_string) return edi_document @@ -173,15 +171,13 @@ class ir_edi_document(osv.osv): if edi_url and not edi_document: edi_document = urllib2.urlopen(edi_url).read() - #assert edi_document, _('EDI Document should be provided') + assert edi_document, _('EDI Document should be provided') else: - edi_documents = self.deserialize(edi_document) return self.load_edi(cr, uid, edi_documents, context=context) ir_edi_document() - class edi(object): _name = 'edi' _description = 'edi document handler' @@ -191,7 +187,6 @@ class edi(object): ``edi_import()`` and ``edi_export()`` methods to implement their specific behavior, based on the primitives provided by this superclass.""" - def edi_metadata(self, cr, uid, records, context=None): """Return a list representing the boilerplate EDI structure for exporting the record in the given browse_rec_list, including @@ -221,11 +216,10 @@ class edi(object): 'file_name': attachment.datas_fname, }) version = [] - for ver in release.major_version.split('.'): version.append(ver) - data_ids = model_data.search(cr, uid, [('res_id','=',record.id)]) + data_ids = model_data.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) if len(data_ids): xml_obj = model_data.browse(cr,uid,data_ids[0]) @@ -261,12 +255,9 @@ class edi(object): else: xml_id = record._name db_uuid = '%s:%s' %(db_uuid, xml_id) - - name = record.name return [db_uuid, name] - def edi_o2m(self, cr, uid, records, context=None): """Return a list representing a O2M EDI value for the browse_records from the given browse_record_list. @@ -289,7 +280,6 @@ class edi(object): return dict_list - def edi_m2m(self, cr, uid, records, context=None): """Return a list representing a M2M EDI value for the browse_records from the given browse_record_list. @@ -310,8 +300,6 @@ class edi(object): return dict_list - - def edi_export(self, cr, uid, records, edi_struct=None, context=None): """Returns a list of dicts representing an edi.document containing the browse_records with ``ids``, using the generic algorithm. @@ -336,19 +324,15 @@ class edi(object): if context is None: context = {} _fields = self.fields_get(cr, uid, context=context) - - fields_to_export = edi_struct and edi_struct.keys() or _fields.keys() edi_dict_list = [] value = None for row in records: edi_dict = {} edi_dict.update(self.edi_metadata(cr, uid, [row], context=context)[0]) - for field in fields_to_export: cols = _fields[field] if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): - pass else: if cols['type'] == 'many2one': @@ -364,10 +348,8 @@ class edi(object): value = getattr(row, field) edi_dict[field] = value edi_dict_list.append(edi_dict) - return edi_dict_list - def edi_import(self, cr, uid, edi_document, context=None): """Imports a list of dicts representing an edi.document, using the @@ -417,18 +399,6 @@ class edi(object): """ # generic implementation! - def convert(data): - if isinstance(data, unicode): - return str(data) - elif isinstance(data, dict): - return dict(map(convert, data.iteritems())) - elif isinstance(data, (list, tuple, set, frozenset)): - return type(data)(map(convert, data)) - else: - return data - #edi_document = convert(edi_document) - - fields = edi_document.keys() fields_to_import = [] data_line = [] @@ -437,23 +407,15 @@ class edi(object): current_module = 'edi_import' values = {} for field in edi_document.keys(): - if field.startswith('__') and field == '__id': - db_uuid, xml_ids = tuple(edi_document[field].split(':')) - - elif not field.startswith('__'): - - + if not field.startswith('__'): fields_to_import.append(field) edi_field_value = edi_document[field] if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): - pass else: if _fields[field]['type'] in ('many2one', 'many2many'): - if _fields[field]['type'] == 'many2one': edi_parent_documents = [edi_field_value] - else: edi_parent_documents = edi_field_value @@ -469,7 +431,6 @@ class edi(object): if data_ids: for data in model_data.browse(cr, uid, [data_ids[0][0]], context=context): parent_lines.append(data.res_id) - print "..............................................",data.res_id else: #Perform name_search(name) to look for a record that matches the #given m2o name. If only one record is found, create the missing @@ -506,8 +467,6 @@ class edi(object): for m2m_id in parent_lines: many2many_ids.append((4,m2m_id)) values[field] = many2many_ids - - elif _fields[field]['type'] == 'one2many': #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 @@ -519,24 +478,15 @@ class edi(object): relations = [] relation_object = self.pool.get(_fields[field]['relation']) for edi_relation_document in edi_field_value: - '''print "pppppppppppppppppppppppppppppppppppppppppp" - print edi_field_value - print ":::::::::::::::::::::::::::::::::::::::::::::" - print edi_relation_document - print edi_parent_document - print "ppppppppppppppppppppppppppppppppppp" if edi_relation_document['__id'].find(':') != -1: db_uuid, xml_id = tuple(edi_relation_document['__id'].split(':')) - print xml_id data_ids = model_data.name_search(cr, uid, xml_id) - print "dddddddddddddddddddddddddddddddddddddddddddddd",data_ids if data_ids: for data in model_data.browse(cr,uid,[data_ids[0][0]]): relations.append(data.res_id) - else:''' - r = relation_object.edi_import(cr, uid, edi_relation_document, context=context) - relations.append(r) - print "relation id ssssssssssssssssssss",relations + else: + r = relation_object.edi_import(cr, uid, edi_relation_document, context=context) + relations.append(r) one2many_ids = [] for o2m_id in relations: one2many_ids.append((4,o2m_id)) @@ -544,8 +494,5 @@ class edi(object): else: values[field] = edi_field_value - for val in values.keys(): - print '::::::::::::::::::::::',val,'::::::::::;;;;;;;;;;;;;;;;;;;;;;' - print values[val] return model_data._update(cr, uid, self._name, current_module, values, context=context) # vim: ts=4 sts=4 sw=4 si et \ No newline at end of file diff --git a/openerp/addons/base/test/test_edi_documents.yml b/openerp/addons/base/test/test_edi_documents.yml index 1b66caaf4dc..06656800d4f 100644 --- a/openerp/addons/base/test/test_edi_documents.yml +++ b/openerp/addons/base/test/test_edi_documents.yml @@ -4,13 +4,11 @@ !python {model: ir.edi.document}: | partner_obj = self.pool.get('res.partner') partner_ids = partner_obj.search(cr, uid, []) - if partner_ids: partners = partner_obj.browse(cr, uid, partner_ids) tokens = self.export_edi(cr, uid, partners) - print '>>>>>>>>>>',tokens for token in tokens: document = self.get_document(cr, uid, token, context=context) a = self.import_edi(cr, uid, edi_document = document) - print '////////////',a + From 5ac271fa1fa85fc24a34c76ca5258d45a422955f Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Mon, 27 Jun 2011 13:59:45 +0530 Subject: [PATCH 16/56] [IMP] edi: improve metadata function to create xml record if does not found bzr revid: hmo@tinyerp.com-20110627082945-xtoibwmlsl8187yg --- openerp/addons/base/ir/ir_edi.py | 50 ++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index ad9fd2a2adf..8db5b397116 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -26,6 +26,9 @@ import time import base64 import urllib2 import openerp.release as release + +edi_module = 'edi_import' + def safe_unique_id(model, database_id): """Generate a unique string to represent a (model,database id) pair without being too long, without revealing the database id, and @@ -201,8 +204,9 @@ class edi(object): - __version': a list of components for the version - __attachments': a list (possibly empty) of dicts describing the files attached to this record. """ - # generic implementation! - model_data = self.pool.get('ir.model.data') + if context is None: + context = {} + model_data_pool = self.pool.get('ir.model.data') data_ids = [] attachment_object = self.pool.get('ir.attachment') edi_dict_list = [] @@ -214,6 +218,7 @@ class edi(object): except: pass version.append(ver) + for record in records: attachment_ids = attachment_object.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)]) attachment_dict_list = [] @@ -225,21 +230,32 @@ class edi(object): }) - data_ids = model_data.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) - + data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) + uuid = safe_unique_id(record._name, record.id) + if len(data_ids): - xml_record = model_data.browse(cr, uid, data_ids[0]) - uuid = safe_unique_id(record._name, record.id) - db_uuid = '%s:%s.%s' % (uuid, xml_record.module, xml_record.name) - + xml_record_id = data_ids[0] + else: + xml_record_id = model_data_pool.create(cr, uid, { + 'name': uuid, + 'model': record._name, + 'module': edi_module, + 'res_id': record.id}, context=context) + + xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) + db_uuid = '%s:%s.%s' % (uuid, xml_record.module, xml_record.name) + edi_dict = { - '__model' : record._name, - '__module' : record._module, '__id': db_uuid, '__last_update': False, #record.write_date, #TODO: convert into UTC - '__version': version, - '__attachments': attachment_dict_list } + if not context.get('o2m_export'): + edi_dict.update({ + '__model' : record._name, + '__module' : record._module, + '__version': version, + '__attachments': attachment_dict_list + }) edi_dict_list.append(edi_dict) return edi_dict_list @@ -278,11 +294,14 @@ class edi(object): """ # generic implementation! dict_list = [] - + if context is None: + context = {} + ctx = context.copy() + ctx.update({'o2m_export':True}) for record in records: model_obj = self.pool.get(record._name) - dict_list += model_obj.edi_export(cr, uid, [record], context=context) + dict_list += model_obj.edi_export(cr, uid, [record], context=ctx) return dict_list @@ -410,7 +429,6 @@ class edi(object): data_line = [] model_data = self.pool.get('ir.model.data') _fields = self.fields_get(cr, uid, context=context) - current_module = 'edi_import' values = {} for field in edi_document.keys(): if not field.startswith('__'): @@ -500,5 +518,5 @@ class edi(object): else: values[field] = edi_field_value - return model_data._update(cr, uid, self._name, current_module, values, context=context) + return model_data._update(cr, uid, self._name, edi_module, values, context=context) # vim: ts=4 sts=4 sw=4 si et From 25dcd09489096441f2588b06db2ad23bab0fd462 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Mon, 27 Jun 2011 17:51:45 +0530 Subject: [PATCH 17/56] [IMP] EDI: introduce new methods in edi class edi_import_relation: create record of relation object if does not match with relation value edi_xml_id: generate xml id with unique db id. create record of ir.model.data if xml id is not found for particular record. bzr revid: hmo@tinyerp.com-20110627122145-v2d2wtkjut68rcba --- openerp/addons/base/ir/ir_edi.py | 74 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 8db5b397116..9672b9a2207 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -190,6 +190,22 @@ class edi(object): Classes that inherit from this mixin class should override the ``edi_import()`` and ``edi_export()`` methods to implement their specific behavior, based on the primitives provided by this superclass.""" + + def edi_xml_id(self, cr, uid, record, context=None): + model_data_pool = self.pool.get('ir.model.data') + uuid = safe_unique_id(record._name, record.id) + data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) + if len(data_ids): + xml_record_id = data_ids[0] + else: + xml_record_id = model_data_pool.create(cr, uid, { + 'name': uuid, + 'model': record._name, + 'module': edi_module, + 'res_id': record.id}, context=context) + xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) + db_uuid = '%s:%s.%s' % (uuid, xml_record.module, xml_record.name) + return db_uuid def edi_metadata(self, cr, uid, records, context=None): """Return a list representing the boilerplate EDI structure for @@ -206,7 +222,6 @@ class edi(object): """ if context is None: context = {} - model_data_pool = self.pool.get('ir.model.data') data_ids = [] attachment_object = self.pool.get('ir.attachment') edi_dict_list = [] @@ -230,21 +245,7 @@ class edi(object): }) - data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) - uuid = safe_unique_id(record._name, record.id) - - if len(data_ids): - xml_record_id = data_ids[0] - else: - xml_record_id = model_data_pool.create(cr, uid, { - 'name': uuid, - 'model': record._name, - 'module': edi_module, - 'res_id': record.id}, context=context) - - xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) - db_uuid = '%s:%s.%s' % (uuid, xml_record.module, xml_record.name) - + db_uuid = self.edi_xml_id(cr, uid, record, context=context) edi_dict = { '__id': db_uuid, '__last_update': False, #record.write_date, #TODO: convert into UTC @@ -267,20 +268,13 @@ class edi(object): Exmaple: ['db-uuid:xml-id', 'Partner Name'] """ # generic implementation! - data_object = self.pool.get('ir.model.data') - db_uuid = safe_unique_id(record._name, record.id) - xml_ids = data_object.search(cr, uid, [('model','=',record._name),('res_id','=',record.id)]) - - if xml_ids: - xml_id = data_object.browse(cr, uid, xml_ids[0], context=context) - xml_id = '%s.%s' %(xml_id.module, xml_id.name) - else: - xml_id = record._name - db_uuid = '%s:%s' %(db_uuid, xml_id) - name = record.name + db_uuid = self.edi_xml_id(cr, uid, record, context=context) + relation_model_pool = self.pool.get(record._name) + name = relation_model_pool.name_get(cr, uid, [record.id], context=context) + name = name and name[0][1] or False return [db_uuid, name] - def edi_o2m(self, cr, uid, records, context=None): + def edi_o2m(self, cr, uid, records, edi_struct=None, context=None): """Return a list representing a O2M EDI value for the browse_records from the given browse_record_list. @@ -301,7 +295,7 @@ class edi(object): for record in records: model_obj = self.pool.get(record._name) - dict_list += model_obj.edi_export(cr, uid, [record], context=ctx) + dict_list += model_obj.edi_export(cr, uid, [record], edi_struct=edi_struct, context=ctx) return dict_list @@ -368,12 +362,22 @@ class edi(object): elif cols['type'] == 'many2many': value = self.edi_m2m(cr, uid, record, context=context) elif cols['type'] == 'one2many': - value = self.edi_o2m(cr, uid, record, context=context ) + value = self.edi_o2m(cr, uid, record, edi_struct=edi_struct.get(field, {}), context=context ) else: value = record edi_dict[field] = value edi_dict_list.append(edi_dict) return edi_dict_list + + def edi_import_relation(self, cr, uid, relation_model, relation_value, values={}, context=None): + relation_object = self.pool.get(relation_model) + relation_ids = relation_object.name_search(cr, uid, relation_value, context=context) + if relation_ids and len(relation_ids) == 1: + relation_id = relation_ids[0][0] + else: + values.update({'name': relation_value}) + relation_id = relation_object.create(cr, uid, values, context=context) + return relation_id def edi_import(self, cr, uid, edi_document, context=None): @@ -465,13 +469,9 @@ class edi(object): #multiple results are found, go to next step. #Create the new record using the only field value that is known: the #name, and create the ir.model.data entry to map to it. - - relation_object = self.pool.get(_fields[field]['relation']) - relation_ids = relation_object.search(cr, uid, [('name','=',edi_parent_document[1])]) - if relation_ids and len(relation_ids) == 1: - relation_id = relation_ids[0] - else: - relation_id = relation_object.create(cr, uid, {'name': edi_parent_document[1]}, context=context) + relation_model = _fields[field]['relation'] + relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], context=context) + relation_object = self.pool.get(relation_model) model_data.create(cr, uid, { 'name': xml_id, 'model': relation_object._name, From 3be8619d32f3ca9ca3859fde34585ccb3f3d3ae8 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Wed, 29 Jun 2011 12:10:37 +0530 Subject: [PATCH 18/56] [IMP] web_service: introduce EDI Services to provide service like get_edi_document, import_edi_document, import_edi_url bzr revid: hmo@tinyerp.com-20110629064037-2wb76qua48m1g2bh --- openerp/service/web_services.py | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 39457807868..37dadc195fb 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -37,6 +37,47 @@ import openerp.modules import locale import logging from cStringIO import StringIO +import threading + +class edi(netsvc.ExportService): + def exp_get_edi_document(self, edi_token): + db_name = getattr(threading.currentThread(), 'dbname', None) + if db_name: + cr = pooler.get_db_only(db_name).cursor() + else: + raise Exception("No database cursor found!") + pool = pooler.get_pool(db_name) + edi_pool = pool.get('ir.edi.document') + return edi_pool.get_document(cr, 1, edi_token) + + def exp_import_edi_document(self, db, uid, passwd, edi_document, context=None): + cr = pooler.get_db_only(db).cursor() + pool = pooler.get_pool(db) + edi_pool = pool.get('ir.edi.document') + return edi_pool.import_edi(cr, uid, edi_document=edi_document, context=context) + + def exp_import_edi_url(self, db, uid, passwd, edi_url, context=None): + cr = pooler.get_db_only(db).cursor() + pool = pooler.get_pool(db) + edi_pool = pool.get('ir.edi.document') + return edi_pool.import_edi(cr, uid, edi_url=edi_url, context=context) + + def __init__(self, name="edi"): + netsvc.ExportService.__init__(self, name) + self.joinGroup("web-services") + + def dispatch(self, method, auth, params): + if method in ['import_edi_document', 'import_edi_url']: + (db, uid, passwd ) = params[0:3] + security.check(db, uid, passwd) + elif method in ['get_edi_document']: + # params = params + # No security check for these methods + pass + else: + raise KeyError("Method not found: %s" % method) + fn = getattr(self, 'exp_'+method) + return fn(*params) class db(netsvc.ExportService): def __init__(self, name="db"): @@ -763,6 +804,7 @@ def start_web_services(): objects_proxy() wizard() report_spool() + edi() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 784b6af17c37b278c30c8b8d14e8ad3be9751abb Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Wed, 29 Jun 2011 15:49:28 +0530 Subject: [PATCH 19/56] [FIX] edi: In export_edi method, edi_struct should be have blank dict if None value bzr revid: hmo@tinyerp.com-20110629101928-djc9hvlm0pmqm362 --- openerp/addons/base/ir/ir_edi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 9672b9a2207..0ee29e13a3a 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -342,6 +342,8 @@ class edi(object): if context is None: context = {} + if edi_struct is None: + edi_struct = {} _fields = self.fields_get(cr, uid, context=context) fields_to_export = edi_struct and edi_struct.keys() or _fields.keys() edi_dict_list = [] From 70b65e0922b75901d594ac782b95464d0f0affa1 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Mon, 11 Jul 2011 13:30:17 +0530 Subject: [PATCH 20/56] [IMP+FIX] edi_service: fixed wrong method call to get_db into import_edi_url service and return list of tuple (mobel,record_id) from load_edi function. it will be useful to redirect document after import successful bzr revid: hmo@tinyerp.com-20110711080017-chgth42d2409o81m --- openerp/addons/base/ir/ir_edi.py | 8 ++++---- openerp/service/web_services.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 0ee29e13a3a..b37abbb7747 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -122,13 +122,14 @@ class ir_edi_document(osv.osv): :param edi_dicts: list of edi_dict """ - + res = [] for edi_document in edi_documents: model = edi_document.get('__model') assert model, _('model should be provided in EDI Dict') model_obj = self.pool.get(model) - model_obj.edi_import(cr, uid, edi_document, context=context) - return True + record_id = model_obj.edi_import(cr, uid, edi_document, context=context) + res.append((model,record_id)) + return res def deserialize(self, edi_document_string): """ Deserialized the edi document string @@ -175,7 +176,6 @@ class ir_edi_document(osv.osv): if edi_url and not edi_document: edi_document = urllib2.urlopen(edi_url).read() - assert edi_document, _('EDI Document should be provided') edi_documents = self.deserialize(edi_document) return self.load_edi(cr, uid, edi_documents, context=context) diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 116eefdd1a1..ec2a3c7507e 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -69,7 +69,7 @@ class edi(netsvc.ExportService): def exp_import_edi_url(self, db, uid, passwd, edi_url, context=None): res = None - cr = pooler.get_db_only(db).cursor() + cr = pooler.get_db(db).cursor() pool = pooler.get_pool(db) edi_pool = pool.get('ir.edi.document') try: From 841cc683c6d0fb91f2dde005cc7c42658bd12442 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Mon, 11 Jul 2011 13:56:03 +0530 Subject: [PATCH 21/56] [IMP] edi: yml of edi document bzr revid: hmo@tinyerp.com-20110711082603-56r1dmtfc2h0lxgt --- openerp/addons/base/test/partner.pdf | 118 ++++++++++++++++++ .../addons/base/test/test_edi_documents.yml | 15 ++- 2 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 openerp/addons/base/test/partner.pdf diff --git a/openerp/addons/base/test/partner.pdf b/openerp/addons/base/test/partner.pdf new file mode 100644 index 00000000000..1b1c7f0daac --- /dev/null +++ b/openerp/addons/base/test/partner.pdf @@ -0,0 +1,118 @@ +%PDF-1.3 +%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com +% 'BasicFonts': class PDFDictionary +1 0 obj +% The standard fonts dictionary +<< /F1 2 0 R + /F2 3 0 R + /F3 4 0 R >> +endobj +% 'F1': class PDFType1Font +2 0 obj +% Font Helvetica +<< /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font >> +endobj +% 'F2': class PDFType1Font +3 0 obj +% Font Helvetica-Bold +<< /BaseFont /Helvetica-Bold + /Encoding /WinAnsiEncoding + /Name /F2 + /Subtype /Type1 + /Type /Font >> +endobj +% 'F3': class PDFType1Font +4 0 obj +% Font Times-Roman +<< /BaseFont /Times-Roman + /Encoding /WinAnsiEncoding + /Name /F3 + /Subtype /Type1 + /Type /Font >> +endobj +% 'Page1': class PDFPage +5 0 obj +% Page dictionary +<< /Contents 9 0 R + /MediaBox [ 0 + 0 + 841.8898 + 595.2756 ] + /Parent 8 0 R + /Resources << /Font 1 0 R + /ProcSet [ /PDF + /Text + /ImageB + /ImageC + /ImageI ] >> + /Rotate 0 + /Trans << >> + /Type /Page >> +endobj +% 'R6': class PDFCatalog +6 0 obj +% Document Root +<< /Outlines 10 0 R + /PageMode /UseNone + /Pages 8 0 R + /Type /Catalog >> +endobj +% 'R7': class PDFInfo +7 0 obj +<< /Author (Generated by OpenERP, Fabien Pinckaers) + /CreationDate (20100421145542) + /Keywords () + /Producer (ReportLab http://www.reportlab.com) + /Subject (\(unspecified\)) + /Title (Partner) >> +endobj +% 'R8': class PDFPages +8 0 obj +% page tree +<< /Count 1 + /Kids [ 5 0 R ] + /Type /Pages >> +endobj +% 'R9': class PDFStream +9 0 obj +% page stream +<< /Filter [ /ASCII85Decode + /FlateDecode ] + /Length 524 >> +stream +Gat=(?#P3ie[>S,B2ME\)S7:@oo6@nF8`I+h##OY3a-n#Qm!>5jiD.9pQ@E1#Xj$=)27)F3RmD%fbqZh,T`gJd0/,?A3`0:_VVm`"l`>oOf9p&HGF7>WCGVJoD2&_+c#BDb)/&0>>rL5@]KY$9YOAqZHTJq8k]!o+?4a5\-KbB#NYJC'`~>endstream + +endobj +% 'R10': class PDFOutlines +10 0 obj +<< /Count 0 + /Type /Outlines >> +endobj +xref +0 11 +0000000000 65535 f +0000000113 00000 n +0000000233 00000 n +0000000398 00000 n +0000000573 00000 n +0000000740 00000 n +0000001017 00000 n +0000001152 00000 n +0000001395 00000 n +0000001500 00000 n +0000002169 00000 n +trailer +<< /ID + % ReportLab generated PDF document -- digest (http://www.reportlab.com) + [(\210\344\006bE\230CUUi\010[\)Qif) (\210\344\006bE\230CUUi\010[\)Qif)] + + /Info 7 0 R + /Root 6 0 R + /Size 11 >> +startxref +2221 +%%EOF diff --git a/openerp/addons/base/test/test_edi_documents.yml b/openerp/addons/base/test/test_edi_documents.yml index 85a350bf3a9..03dbe623ede 100644 --- a/openerp/addons/base/test/test_edi_documents.yml +++ b/openerp/addons/base/test/test_edi_documents.yml @@ -1,5 +1,5 @@ - - create partner + I create partner - !record {model: res.partner, id: res_partner_test33}: name: Ivan @@ -17,7 +17,7 @@ company_id: base.main_company - - I am creating one Document to be stored in ". + I am creating one Document to be attached in partner ". - !record {model: 'ir.attachment', id: document_4}: name : "My partner document" @@ -25,7 +25,7 @@ type : binary - - Attach a pdf file in "My partner document" + I attach a pdf file in "My partner document" - !python {model: 'ir.attachment'}: | import addons @@ -34,15 +34,14 @@ self.write(cr, uid, [ref("document_4")], {'res_id':ref("res_partner_test33"), 'datas_fname': 'partner.pdf', 'datas' : base64.encodestring(pdf_file.read())}, context) - - Testing import and export + I am Tesing of EDI functionality. so first export partner and than import it again. - !python {model: ir.edi.document}: | partner_obj = self.pool.get('res.partner') partners = [partner_obj.browse(cr, uid, ref("res_partner_test33"))] tokens = self.export_edi(cr, uid, partners) - for token in tokens: - document = self.get_document(cr, uid, token, context=context) - print document - a = self.import_edi(cr, uid, edi_document = document) + assert tokens, 'Token is not generated.' + document = self.get_document(cr, uid, tokens[0], context=context) + a = self.import_edi(cr, uid, edi_document = document) From db70049e4d4ed37c83d71f3cafc8d5722a27b6ad Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 15 Jul 2011 14:05:09 +0530 Subject: [PATCH 22/56] [ADD] base: add configuration parameter to store web.base.url bzr revid: hmo@tinyerp.com-20110715083509-j0dyh2dq1j9c9t0q --- openerp/addons/base/base_data.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 3f8208b8ec4..5b9a1e07e7f 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -1,6 +1,10 @@ + + web.base.url + http://localhost:8002 + ir.ui.menu.tree ir.ui.menu From 045b42b03a65b6120b2457f8afd64980514fb230 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 15 Jul 2011 14:05:56 +0530 Subject: [PATCH 23/56] [FIX] base.ir_edi: value of function fields should be exported on EDI Document bzr revid: hmo@tinyerp.com-20110715083556-u60zjo42ync4d47w --- openerp/addons/base/ir/ir_edi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index b37abbb7747..208f69dabac 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -356,9 +356,9 @@ class edi(object): record = getattr(row, field) if not record: continue - if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): - # Do not Export Function Fields and related fields - continue + #if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): + # # Do not Export Function Fields and related fields + # continue elif cols['type'] == 'many2one': value = self.edi_m2o(cr, uid, record, context=context) elif cols['type'] == 'many2many': From 1d7eb0cdc25ce45a0d48881f5732f1b010d47ff1 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 15 Jul 2011 15:20:59 +0530 Subject: [PATCH 24/56] [FIX] service: put autocommit true on EDI import service to store imported data bzr revid: hmo@tinyerp.com-20110715095059-96rrh2pu4acevzj4 --- openerp/addons/base/ir/ir_edi.py | 3 +-- openerp/service/web_services.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 208f69dabac..7347625c622 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -519,6 +519,5 @@ class edi(object): values[field] = one2many_ids else: values[field] = edi_field_value - - return model_data._update(cr, uid, self._name, edi_module, values, context=context) + return self.pool.get(self._name).create(cr, uid, values, context=context) # vim: ts=4 sts=4 sw=4 si et diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index ec2a3c7507e..58b31ede7ff 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -62,6 +62,7 @@ class edi(netsvc.ExportService): pool = pooler.get_pool(db) edi_pool = pool.get('ir.edi.document') try: + cr.autocommit(True) res = edi_pool.import_edi(cr, uid, edi_document=edi_document, context=context) finally: cr.close() @@ -73,6 +74,7 @@ class edi(netsvc.ExportService): pool = pooler.get_pool(db) edi_pool = pool.get('ir.edi.document') try: + cr.autocommit(True) res = edi_pool.import_edi(cr, uid, edi_url=edi_url, context=context) finally: cr.close() From 69259d01711f41349f251cbc610087c9a3acfc0b Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 15 Jul 2011 16:27:39 +0530 Subject: [PATCH 25/56] [FIX] base.ir_edi: call _update method of ir.model.data on import bzr revid: hmo@tinyerp.com-20110715105739-6vvwjlmghh19pjsu --- openerp/addons/base/ir/ir_edi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 7347625c622..1d13b84aedc 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -519,5 +519,5 @@ class edi(object): values[field] = one2many_ids else: values[field] = edi_field_value - return self.pool.get(self._name).create(cr, uid, values, context=context) + return model_data._update(cr, uid, self._name, edi_module, values, context=context) # vim: ts=4 sts=4 sw=4 si et From 7417f09da100f46f77dc78d75af3ef5b69d5ff1e Mon Sep 17 00:00:00 2001 From: "Hardik Ansodariy (OpenERP)" Date: Fri, 19 Aug 2011 18:54:22 +0530 Subject: [PATCH 26/56] [IMP]: made changes and removed unwanted code bzr revid: han@tinyerp.com-20110819132422-5frpi82rdhczpykb --- openerp/addons/base/ir/ir_edi.py | 18 +++++++++++++----- openerp/addons/base/res/partner/partner.py | 4 ++-- openerp/service/web_services.py | 6 ++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 225df8d9944..44d6d8f4303 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -26,6 +26,8 @@ import time import base64 import urllib2 import openerp.release as release +from tools.translate import _ +import netsvc edi_module = 'edi_import' @@ -122,13 +124,19 @@ class ir_edi_document(osv.osv): :param edi_dicts: list of edi_dict """ + module_obj =self.pool.get('ir.module.module') res = [] for edi_document in edi_documents: - model = edi_document.get('__model') - assert model, _('model should be provided in EDI Dict') - model_obj = self.pool.get(model) - record_id = model_obj.edi_import(cr, uid, edi_document, context=context) - res.append((model,record_id)) + module = edi_document.get('__module') + module_id = module_obj.search(cr, uid, [('name','=',module),('state','=','installed')]) + if module_id: + model = edi_document.get('__model') + assert model, _('model should be provided in EDI Dict') + model_obj = self.pool.get(model) + record_id = model_obj.edi_import(cr, uid, edi_document, context=context) + res.append((model,record_id)) + else: + raise osv.except_osv(_('Invalid action !'), _('Module is not Installed')) return res def deserialize(self, edi_document_string): diff --git a/openerp/addons/base/res/partner/partner.py b/openerp/addons/base/res/partner/partner.py index de8fce3c5e9..e1e71f4996d 100644 --- a/openerp/addons/base/res/partner/partner.py +++ b/openerp/addons/base/res/partner/partner.py @@ -111,7 +111,7 @@ class res_partner(osv.osv): _order = "name" _columns = { 'name': fields.char('Name', size=128, required=True, select=True), - 'opt_out':fields.boolean('Opt-out'), + 'opt_out':fields.boolean('Opt-out', help='After enable opt-out, Automated Mail will not be sent to provided email address of partner'), 'date': fields.date('Date', select=1), 'title': fields.many2one('res.partner.title','Partner Firm'), 'parent_id': fields.many2one('res.partner','Parent Partner'), @@ -150,7 +150,7 @@ class res_partner(osv.osv): 'customer': lambda *a: 1, 'category_id': _default_category, 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c), - 'opt_out' : False + 'opt_out' : True } def copy(self, cr, uid, id, default={}, context={}): name = self.read(cr, uid, [id], ['name'])[0]['name'] diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 58b31ede7ff..0e8acc6bb7a 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -40,10 +40,8 @@ from cStringIO import StringIO import threading class edi(netsvc.ExportService): - def exp_get_edi_document(self, edi_token, db_name=None): + def exp_get_edi_document(self, edi_token, db_name): res = None - if not db_name: - db_name = getattr(threading.currentThread(), 'dbname', None) if db_name: cr = pooler.get_db(db_name).cursor() else: @@ -74,8 +72,8 @@ class edi(netsvc.ExportService): pool = pooler.get_pool(db) edi_pool = pool.get('ir.edi.document') try: - cr.autocommit(True) res = edi_pool.import_edi(cr, uid, edi_url=edi_url, context=context) + cr.commit() finally: cr.close() return res From fc5bf7ae26b6e7cea45cae8e072bc78ee1dd28c3 Mon Sep 17 00:00:00 2001 From: "Hardik Ansodariy (OpenERP)" Date: Tue, 23 Aug 2011 17:06:32 +0530 Subject: [PATCH 27/56] [IMP] bzr revid: han@tinyerp.com-20110823113632-3k859oxwq5k7kwzw --- openerp/addons/base/ir/ir_edi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 44d6d8f4303..443a1d12ec6 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -129,14 +129,14 @@ class ir_edi_document(osv.osv): for edi_document in edi_documents: module = edi_document.get('__module') module_id = module_obj.search(cr, uid, [('name','=',module),('state','=','installed')]) - if module_id: + if not module_id: + raise Exception("The document you are trying to import requires the OpenERP" +module+ "application") + else: model = edi_document.get('__model') assert model, _('model should be provided in EDI Dict') model_obj = self.pool.get(model) record_id = model_obj.edi_import(cr, uid, edi_document, context=context) res.append((model,record_id)) - else: - raise osv.except_osv(_('Invalid action !'), _('Module is not Installed')) return res def deserialize(self, edi_document_string): @@ -323,7 +323,7 @@ class edi(object): dict_list = [] for record in records: - dict_list.append(self.edi_o2m(cr, uid, [record], context=None)) + dict_list.append(self.edi_m2o(cr, uid, [record], context=None)) return dict_list From 2b0109060c081d9a2b80d6317b0d2fb8a925083b Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Mon, 29 Aug 2011 13:17:32 +0530 Subject: [PATCH 28/56] [IMP] ir.edi: improve edi_import_relation and edi_xml_id with better xml_id, uuid bzr revid: hmo@tinyerp.com-20110829074732-lq2q9k2wh2ywnooy --- openerp/addons/base/ir/ir_edi.py | 117 +++++++++++++++---------------- openerp/service/web_services.py | 2 + 2 files changed, 58 insertions(+), 61 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 9af5bb325f8..2fd11338ffb 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -29,8 +29,6 @@ import openerp.release as release from tools.translate import _ import netsvc -edi_module = 'edi_import' - def safe_unique_id(database_id, model, record_id): """Generate a unique string to represent a (database id,model,record_id) pair without being too long, without revealing the database id, and @@ -42,7 +40,7 @@ def safe_unique_id(database_id, model, record_id): # finally, use the b64-encoded folded digest as ID part of the unique ID: digest = base64.urlsafe_b64encode(digest) - return '%s-%s' % (model,digest) + return '%s-%s' % (model.replace('.','_'), digest) class ir_edi_document(osv.osv): _name = 'ir.edi.document' @@ -185,6 +183,15 @@ class edi(object): ``edi_import()`` and ``edi_export()`` methods to implement their specific behavior, based on the primitives provided by this superclass.""" + def record_xml_id(self, cr, uid, record, context=None): + model_data_pool = self.pool.get('ir.model.data') + data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) + if not data_ids: + return False + xml_record_id = data_ids[0] + xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) + return xml_record.module, xml_record.name + def edi_xml_id(self, cr, uid, record, context=None): """ Generate a unique string to represent a pair of (the database's UUID, the XML ID). @@ -206,20 +213,21 @@ class edi(object): model_data_pool = self.pool.get('ir.model.data') db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) - if len(data_ids): - xml_record_id = data_ids[0] - xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) - uuid = '%s:%s' % (db_uuid, xml_record.name) + _record_xml = self.record_xml_id(cr, uid, record, context=context) + if _record_xml: + module, xml_id = _record_xml + xml_id = '%s:%s.%s' % (db_uuid, module, xml_id) else: - xml_id = safe_unique_id(db_uuid, record._name, record.id) - uuid = '%s:%s' % (db_uuid, xml_id) + uuid = safe_unique_id(db_uuid, record._name, record.id) + xml_id = '%s:%s.%s' % (db_uuid, record._module, uuid) + assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) + module, xml_id2 = xml_id.split('.') xml_record_id = model_data_pool.create(cr, uid, { - 'name': xml_id, + 'name': xml_id2, 'model': record._name, - 'module': uuid, + 'module': module, 'res_id': record.id}, context=context) - return uuid + return xml_id def edi_metadata(self, cr, uid, records, context=None): """Return a list representing the boilerplate EDI structure for @@ -385,14 +393,29 @@ class edi(object): edi_dict_list.append(edi_dict) return edi_dict_list - def edi_import_relation(self, cr, uid, relation_model, relation_value, values={}, context=None): + def edi_import_relation(self, cr, uid, relation_model, relation_value, xml_id=None, context=None): + relation_id = False + model_data = self.pool.get('ir.model.data') relation_object = self.pool.get(relation_model) relation_ids = relation_object.name_search(cr, uid, relation_value, context=context) if relation_ids and len(relation_ids) == 1: relation_id = relation_ids[0][0] - else: - values.update({'name': relation_value}) + values = {relation_object._rec_name: relation_value} + if not relation_id: relation_id = relation_object.create(cr, uid, values, context=context) + + relation_record = relation_object.browse(cr, uid, relation_id, context=context) + record_xml = self.record_xml_id(cr, uid, relation_record, context=context) + if record_xml: + module, xml_id = record_xml + xml_id = '%s.%s' % (module, xml_id) + if not xml_id: + xml_id = self.edi_xml_id(cr, uid, relation_record, context=context) + if xml_id: + assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) + module, xml_id2 = xml_id.split('.') + #TODO: update record from values + #relation_id = model_data._update(cr, uid, relation_model, module, values, xml_id=xml_id, context=context) return relation_id def edi_import(self, cr, uid, edi_document, context=None): @@ -443,24 +466,28 @@ class edi(object): connect it to the parent record via a write value like (4, db_id). """ # generic implementation! - fields = edi_document.keys() fields_to_import = [] data_line = [] model_data = self.pool.get('ir.model.data') _fields = self.fields_get(cr, uid, context=context) values = {} + xml_id = edi_document['__id'] + assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) + module, xml_id2 = xml_id.split('.') for field in edi_document.keys(): if not field.startswith('__'): fields_to_import.append(field) edi_field_value = edi_document[field] + field_type = _fields[field]['type'] + relation_model = _fields[field].get('relation') if not edi_field_value: continue if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): # DO NOT IMPORT FUNCTION FIELD AND RELATED FIELD continue - elif _fields[field]['type'] in ('many2one', 'many2many'): - if _fields[field]['type'] == 'many2one': + elif field_type in ('many2one', 'many2many'): + if field_type == 'many2one': edi_parent_documents = [edi_field_value] else: edi_parent_documents = edi_field_value @@ -468,41 +495,19 @@ class edi(object): parent_lines = [] for edi_parent_document in edi_parent_documents: - #Look in ir.model.data for a record that matches the db_id. - #If found, replace the m2o value with the correct database ID and stop. - #If not found, continue to next step - xml_id = edi_parent_document[0] - data_ids = model_data.name_search(cr, uid, xml_id) - if data_ids: - for data in model_data.browse(cr, uid, [data_ids[0][0]], context=context): - parent_lines.append(data.res_id) - else: - #Perform name_search(name) to look for a record that matches the - #given m2o name. If only one record is found, create the missing - #ir.model.data record to link it to the db_id, and the replace the m2o - #value with the correct database ID, then stop. If zero result or - #multiple results are found, go to next step. - #Create the new record using the only field value that is known: the - #name, and create the ir.model.data entry to map to it. - relation_model = _fields[field]['relation'] - relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], context=context) - relation_object = self.pool.get(relation_model) - relation_record = relation_object.browse(cr, uid, relation_id, context=context) - self.edi_xml_id(cr, uid, relation_record, context=context) - - parent_lines.append(relation_id) - + relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], edi_parent_document[0], context=context) + parent_lines.append(relation_id) if len(parent_lines): - if _fields[field]['type'] == 'many2one': + if field_type == 'many2one': values[field] = parent_lines[0] else: many2many_ids = [] for m2m_id in parent_lines: - many2many_ids.append((4,m2m_id)) + many2many_ids.append((4, m2m_id)) values[field] = many2many_ids - elif _fields[field]['type'] == 'one2many': + elif field_type == 'one2many': #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 #using a write value like (4,db_id). @@ -511,22 +516,12 @@ class edi(object): #and use it to connect to the parent via a write value like (4, db_id). relations = [] - relation_object = self.pool.get(_fields[field]['relation']) + relation_object = self.pool.get(relation_model) for edi_relation_document in edi_field_value: - if edi_relation_document['__id'].find(':') != -1: - db_uuid, xml_id = tuple(edi_relation_document['__id'].split(':')) - data_ids = model_data.name_search(cr, uid, xml_id) - if data_ids: - for data in model_data.browse(cr,uid,[data_ids[0][0]]): - relations.append(data.res_id) - else: - r = relation_object.edi_import(cr, uid, edi_relation_document, context=context) - relations.append(r) - one2many_ids = [] - for o2m_id in relations: - one2many_ids.append((4,o2m_id)) - values[field] = one2many_ids + relation_id = relation_object.edi_import(cr, uid, edi_relation_document, context=context) + relations.append((4, relation_id)) + values[field] = relations else: values[field] = edi_field_value - return model_data._update(cr, uid, self._name, edi_module, values, context=context) + return model_data._update(cr, uid, self._name, module, values, xml_id=xml_id, context=context) # vim: ts=4 sts=4 sw=4 si et diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index f6b4ff23df0..5be6629bb49 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -63,6 +63,7 @@ class edi(netsvc.ExportService): res = edi_pool.import_edi(cr, uid, edi_document=edi_document, context=context) cr.commit() except: + print traceback.format_exc() cr.rollback() finally: cr.close() @@ -77,6 +78,7 @@ class edi(netsvc.ExportService): res = edi_pool.import_edi(cr, uid, edi_url=edi_url, context=context) cr.commit() except: + print traceback.format_exc() cr.rollback() finally: cr.close() From 3ba75e5d50b95f6ba01400afcf1c31ce3ee0f10e Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Thu, 8 Sep 2011 15:15:46 +0530 Subject: [PATCH 29/56] [IMP] base.ir_edi: dont use fields_get or _columns to get the list of fields, use the new "self._all_columns" (map of column_info records) which also contains inherited fields and is already cached (see in ORM for the definition of the column_info object) bzr revid: hmo@tinyerp.com-20110908094546-4zy2dbqk8zyrzr26 --- openerp/addons/base/ir/ir_edi.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 2fd11338ffb..5588e4d041f 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -366,26 +366,27 @@ class edi(object): context = {} if edi_struct is None: edi_struct = {} - _fields = self.fields_get(cr, uid, context=context) - fields_to_export = edi_struct and edi_struct.keys() or _fields.keys() + _columns = self._all_columns + fields_to_export = edi_struct and edi_struct.keys() or _columns.keys() edi_dict_list = [] value = None for row in records: edi_dict = {} edi_dict.update(self.edi_metadata(cr, uid, [row], context=context)[0]) for field in fields_to_export: - cols = _fields[field] + _column = _columns[field].column + _column_dict = fields.field_to_dict(self, cr, uid, context, _column) record = getattr(row, field) if not record: continue #if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): # # Do not Export Function Fields and related fields # continue - elif cols['type'] == 'many2one': + elif _column_dict['type'] == 'many2one': value = self.edi_m2o(cr, uid, record, context=context) - elif cols['type'] == 'many2many': + elif _column_dict['type'] == 'many2many': value = self.edi_m2m(cr, uid, record, context=context) - elif cols['type'] == 'one2many': + elif _column_dict['type'] == 'one2many': value = self.edi_o2m(cr, uid, record, edi_struct=edi_struct.get(field, {}), context=context ) else: value = record @@ -466,24 +467,26 @@ class edi(object): connect it to the parent record via a write value like (4, db_id). """ # generic implementation! - fields = edi_document.keys() fields_to_import = [] data_line = [] model_data = self.pool.get('ir.model.data') - _fields = self.fields_get(cr, uid, context=context) + _columns = self._all_columns values = {} xml_id = edi_document['__id'] assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) module, xml_id2 = xml_id.split('.') for field in edi_document.keys(): if not field.startswith('__'): + _column = _columns[field].column + _column_dict = fields.field_to_dict(self, cr, uid, context, _column) + fields_to_import.append(field) edi_field_value = edi_document[field] - field_type = _fields[field]['type'] - relation_model = _fields[field].get('relation') + field_type = _column_dict['type'] + relation_model = _column_dict.get('relation') if not edi_field_value: continue - if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): + if _column_dict.has_key('function') or _column_dict.has_key('related_columns'): # DO NOT IMPORT FUNCTION FIELD AND RELATED FIELD continue elif field_type in ('many2one', 'many2many'): From 7ce7230c4010cf5e7099bbd7c34b4cba19b54388 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Tue, 13 Sep 2011 18:24:56 +0530 Subject: [PATCH 30/56] [IMP] base.ir.edi: improve edi import process, split in different methods [ADD] base.res.edi_company: introduce new methods in res.company to import company as parter and export company address bzr revid: hmo@tinyerp.com-20110913125456-j9ipisqu5lf6tdyq --- openerp/addons/base/ir/ir_edi.py | 188 ++++++++++++--------- openerp/addons/base/res/__init__.py | 2 + openerp/addons/base/res/edi_res_company.py | 103 +++++++++++ 3 files changed, 214 insertions(+), 79 deletions(-) create mode 100644 openerp/addons/base/res/edi_res_company.py diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 5588e4d041f..1d36a879036 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -91,7 +91,7 @@ class ir_edi_document(osv.osv): returns the string serialization that is in the database (column: document) for the given edi_token or raise. """ - records = self.name_search(cr, uid, edi_token, context=context) + records = self.name_search(cr, uid, edi_token, operator='=', context=context) if records: record = records[0][0] edi = self.browse(cr, uid, record, context=context) @@ -192,7 +192,7 @@ class edi(object): xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) return xml_record.module, xml_record.name - def edi_xml_id(self, cr, uid, record, context=None): + def edi_xml_id(self, cr, uid, record, xml_id=None, context=None): """ Generate a unique string to represent a pair of (the database's UUID, the XML ID). Each EDI record and each relationship value are represented using a unique @@ -218,8 +218,9 @@ class edi(object): module, xml_id = _record_xml xml_id = '%s:%s.%s' % (db_uuid, module, xml_id) else: - uuid = safe_unique_id(db_uuid, record._name, record.id) - xml_id = '%s:%s.%s' % (db_uuid, record._module, uuid) + if not xml_id: + uuid = safe_unique_id(db_uuid, record._name, record.id) + xml_id = '%s:%s.%s' % (db_uuid, record._module, uuid) assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) module, xml_id2 = xml_id.split('.') xml_record_id = model_data_pool.create(cr, uid, { @@ -337,7 +338,7 @@ class edi(object): dict_list = [] for record in records: - dict_list.append(self.edi_m2o(cr, uid, [record], context=None)) + dict_list.append(self.edi_m2o(cr, uid, record, context=None)) return dict_list @@ -394,30 +395,51 @@ class edi(object): edi_dict_list.append(edi_dict) return edi_dict_list - def edi_import_relation(self, cr, uid, relation_model, relation_value, xml_id=None, context=None): - relation_id = False - model_data = self.pool.get('ir.model.data') - relation_object = self.pool.get(relation_model) - relation_ids = relation_object.name_search(cr, uid, relation_value, context=context) - if relation_ids and len(relation_ids) == 1: - relation_id = relation_ids[0][0] - values = {relation_object._rec_name: relation_value} - if not relation_id: - relation_id = relation_object.create(cr, uid, values, context=context) - relation_record = relation_object.browse(cr, uid, relation_id, context=context) - record_xml = self.record_xml_id(cr, uid, relation_record, context=context) + def edi_get_object_by_name(self, cr, uid, value, model_name, context=None): + openobject = False + model = self.pool.get(model_name) + object_ids = model.name_search(cr, uid, value, operator='=', context=context) + if object_ids and len(object_ids) == 1: + object_id = object_ids[0][0] + openobject = model.browse(cr, uid, object_id, context=context) + return openobject + + def edi_get_object(self, cr, uid, xml_id, model, context=None): + model_data = self.pool.get('ir.model.data') + assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) + module, xml_id2 = xml_id.split('.') + openobject = False + data_ids = model_data.search(cr, uid, [('model','=', model), ('name','=', xml_id2)]) + if data_ids: + model = self.pool.get(model) + data = model_data.browse(cr, uid, data_ids[0], context=context) + openobject = model.browse(cr, uid, data.res_id, context=context) + return openobject + + def edi_import_relation(self, cr, uid, relation_model, relation_value, xml_id=None, context=None): + relation = False + if xml_id: + relation = self.edi_get_object(cr, uid, xml_id, relation_model, context=context) + if not relation: + relation = self.edi_get_object_by_name(cr, uid, relation_value, relation_model, context=context) + if not relation: + relation_model = self.pool.get(relation_model) + values = {} #relation_model.default_get(cr, uid, fields, context=context) + values[relation_model._rec_name] = relation_value + relation_id = relation_model.create(cr, uid, values, context=context) + relation = relation_model.browse(cr, uid, relation_id, context=context) + + record_xml = self.record_xml_id(cr, uid, relation, context=context) if record_xml: module, xml_id = record_xml xml_id = '%s.%s' % (module, xml_id) if not xml_id: - xml_id = self.edi_xml_id(cr, uid, relation_record, context=context) - if xml_id: - assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) - module, xml_id2 = xml_id.split('.') - #TODO: update record from values + xml_id = self.edi_xml_id(cr, uid, relation, context=context) + + #TODO: update record from values ? #relation_id = model_data._update(cr, uid, relation_model, module, values, xml_id=xml_id, context=context) - return relation_id + return relation and relation.id or False def edi_import(self, cr, uid, edi_document, context=None): @@ -467,64 +489,72 @@ class edi(object): connect it to the parent record via a write value like (4, db_id). """ # generic implementation! - fields_to_import = [] - data_line = [] model_data = self.pool.get('ir.model.data') - _columns = self._all_columns - values = {} - xml_id = edi_document['__id'] - assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) - module, xml_id2 = xml_id.split('.') - for field in edi_document.keys(): - if not field.startswith('__'): - _column = _columns[field].column - _column_dict = fields.field_to_dict(self, cr, uid, context, _column) - - fields_to_import.append(field) - edi_field_value = edi_document[field] - field_type = _column_dict['type'] - relation_model = _column_dict.get('relation') - if not edi_field_value: + assert self._name == edi_document['__model'], 'EDI Document could not import. Model of EDI Document and current model are does not match' + def process(datas, model_name): + values = {} + model_pool = self.pool.get(model_name) + xml_id = datas['__id'] + assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) + module, xml_id2 = xml_id.split('.') + _columns = model_pool._all_columns + for field in datas.keys(): + if field not in _columns: continue - if _column_dict.has_key('function') or _column_dict.has_key('related_columns'): - # DO NOT IMPORT FUNCTION FIELD AND RELATED FIELD - continue - elif field_type in ('many2one', 'many2many'): - if field_type == 'many2one': - edi_parent_documents = [edi_field_value] - else: - edi_parent_documents = edi_field_value - - parent_lines = [] - - for edi_parent_document in edi_parent_documents: - relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], edi_parent_document[0], context=context) - parent_lines.append(relation_id) - - if len(parent_lines): + if not field.startswith('__'): + _column = _columns[field].column + _column_dict = fields.field_to_dict(self, cr, uid, context, _column) + + edi_field_value = datas[field] + field_type = _column_dict['type'] + relation_model = _column_dict.get('relation') + #print '???????', field, edi_field_value, relation_model, field_type + if not edi_field_value: + continue + if _column_dict.has_key('function') or _column_dict.has_key('related_columns'): + # DO NOT IMPORT FUNCTION FIELD AND RELATED FIELD + continue + elif field_type == 'one2many': + # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})] + relations = [] + relation_object = self.pool.get(relation_model) + for edi_relation_document in edi_field_value: + relation = self.edi_get_object(cr, uid, edi_relation_document['__id'], relation_model, context=context) + if not relation and edi_relation_document.get('name'): + relation = self.edi_get_object_by_name(cr, uid, edi_relation_document['name'], relation_model, context=context) + if relation: + self.edi_xml_id(cr, uid, relation, \ + xml_id=edi_relation_document['__id'], context=context) + relations.append((4, relation.id)) + res_module, res_xml_id, newrow = process(edi_relation_document, relation_model) + relations.append( (relation and 1 or 0, relation and relation.id or 0, newrow)) + values[field] = relations + elif field_type in ('many2one', 'many2many'): if field_type == 'many2one': - values[field] = parent_lines[0] - + edi_parent_documents = [edi_field_value] else: - many2many_ids = [] - for m2m_id in parent_lines: - many2many_ids.append((4, m2m_id)) - values[field] = many2many_ids - elif field_type == 'one2many': - #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 - #using a write value like (4,db_id). - #If not found via db_id, create a new entry using the same method that - #imports a full EDI record (recursive call!), grab the resulting db id, - #and use it to connect to the parent via a write value like (4, db_id). - - relations = [] - relation_object = self.pool.get(relation_model) - for edi_relation_document in edi_field_value: - relation_id = relation_object.edi_import(cr, uid, edi_relation_document, context=context) - relations.append((4, relation_id)) - values[field] = relations - else: - values[field] = edi_field_value - return model_data._update(cr, uid, self._name, module, values, xml_id=xml_id, context=context) + edi_parent_documents = edi_field_value + + parent_lines = [] + + for edi_parent_document in edi_parent_documents: + relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], edi_parent_document[0], context=context) + parent_lines.append(relation_id) + + if len(parent_lines): + if field_type == 'many2one': + values[field] = parent_lines[0] + + else: + many2many_ids = [] + for m2m_id in parent_lines: + many2many_ids.append((4, m2m_id)) + values[field] = many2many_ids + + else: + values[field] = edi_field_value + return module, xml_id2, values + + module, xml_id, data_values = process(edi_document, self._name) + return model_data._update(cr, uid, self._name, module, data_values, xml_id=xml_id, context=context) # vim: ts=4 sts=4 sw=4 si et diff --git a/openerp/addons/base/res/__init__.py b/openerp/addons/base/res/__init__.py index de00fc54153..6f20b5c4f0e 100644 --- a/openerp/addons/base/res/__init__.py +++ b/openerp/addons/base/res/__init__.py @@ -34,3 +34,5 @@ import res_log import ir_property import res_widget +import edi_res_company + diff --git a/openerp/addons/base/res/edi_res_company.py b/openerp/addons/base/res/edi_res_company.py new file mode 100644 index 00000000000..0eabd39781e --- /dev/null +++ b/openerp/addons/base/res/edi_res_company.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2011 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import osv +from osv import fields +from tools.translate import _ + +class res_company(osv.osv): + _inherit = "res.company" + + def edi_import_as_partner(self, cr, uid, edi_document, values=None, context=None): + """ + import company as a new partner + company_address data used to add address to new partner + + edi_document is a dict to have company datas + edi_document = { + 'company_address': { + 'street': True, + 'street2': True, + 'zip': True, + 'city': True, + 'state_id': True, + 'country_id': True, + 'email': True, + 'phone': True, + + }, + 'company_id': True, + } + values is a dict to have other datas of partner which are need to import of partner record + values = { + 'customer': True, + 'supplier': True, + } + """ + if values is None: + values = {} + partner_pool = self.pool.get('res.partner') + edi_document_partner = { + '__model': 'res.partner', + '__id' : edi_document['company_id'][0], + 'name' : edi_document['company_id'][1], + 'address': [edi_document['company_address']] + } + edi_document_partner.update(values) + return partner_pool.edi_import(cr, uid, edi_document_partner, context=context) + + def edi_export_address(self, cr, uid, records, edi_address_struct=None, context=None): + if context is None: + context = {} + partner_pool = self.pool.get('res.partner') + partner_address_pool = self.pool.get('res.partner.address') + if edi_address_struct is None: + edi_address_struct = { + 'street': True, + 'street2': True, + 'zip': True, + 'city': True, + 'state_id': True, + 'country_id': True, + 'email': True, + 'phone': True, + + } + edi_company_dict = {} + for company in records: + edi_doc = {} + company_address = False + res = partner_pool.address_get(cr, uid, [company.partner_id.id], ['contact', 'invoice']) + addr_id = res['invoice'] or res['contact'] + if addr_id: + address = partner_address_pool.browse(cr, uid, addr_id, context=context) + ctx = context.copy() + ctx.update({'o2m_export':True}) + edi_address_dict_list = partner_address_pool.edi_export(cr, uid, [address], edi_struct=edi_address_struct, context=ctx) + if edi_address_dict_list: + company_address = edi_address_dict_list[0] + edi_doc.update({ + 'company_address': company_address, + #'company_logo': company.logo,#TODO + }) + edi_company_dict[company.id] = edi_doc + return edi_company_dict +res_company() From 701d20fa258ce98476ff75b1afdd2b06638c46e9 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Tue, 13 Sep 2011 18:54:16 +0530 Subject: [PATCH 31/56] [IMP] base.ir.edi: introduce new method: split_xml_id to split module and xml_id from uuid of edi_document bzr revid: hmo@tinyerp.com-20110913132416-1putaa2y3rxzqunc --- openerp/addons/base/ir/ir_edi.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 1d36a879036..062517b1f87 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -29,6 +29,11 @@ import openerp.release as release from tools.translate import _ import netsvc +def split_xml_id(xml_id): + assert len(xml_id.split('.'))==2, \ + _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) + return xml_id.split('.') + def safe_unique_id(database_id, model, record_id): """Generate a unique string to represent a (database id,model,record_id) pair without being too long, without revealing the database id, and @@ -221,8 +226,7 @@ class edi(object): if not xml_id: uuid = safe_unique_id(db_uuid, record._name, record.id) xml_id = '%s:%s.%s' % (db_uuid, record._module, uuid) - assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) - module, xml_id2 = xml_id.split('.') + module, xml_id2 = split_xml_id(xml_id) xml_record_id = model_data_pool.create(cr, uid, { 'name': xml_id2, 'model': record._name, @@ -407,8 +411,7 @@ class edi(object): def edi_get_object(self, cr, uid, xml_id, model, context=None): model_data = self.pool.get('ir.model.data') - assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) - module, xml_id2 = xml_id.split('.') + module, xml_id2 = split_xml_id(xml_id) openobject = False data_ids = model_data.search(cr, uid, [('model','=', model), ('name','=', xml_id2)]) if data_ids: @@ -495,8 +498,7 @@ class edi(object): values = {} model_pool = self.pool.get(model_name) xml_id = datas['__id'] - assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) - module, xml_id2 = xml_id.split('.') + module, xml_id2 = split_xml_id(xml_id) _columns = model_pool._all_columns for field in datas.keys(): if field not in _columns: @@ -508,7 +510,6 @@ class edi(object): edi_field_value = datas[field] field_type = _column_dict['type'] relation_model = _column_dict.get('relation') - #print '???????', field, edi_field_value, relation_model, field_type if not edi_field_value: continue if _column_dict.has_key('function') or _column_dict.has_key('related_columns'): From 0e1765a4d5ac181bb351d8adc3cc40999eab22a5 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Wed, 14 Sep 2011 15:55:05 +0530 Subject: [PATCH 32/56] [FIX] workflow: pass active_model in context on run server action bzr revid: hmo@tinyerp.com-20110914102505-03tzr0ncfsngnqnu --- openerp/workflow/wkf_expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/workflow/wkf_expr.py b/openerp/workflow/wkf_expr.py index 468e5225212..608af34d118 100644 --- a/openerp/workflow/wkf_expr.py +++ b/openerp/workflow/wkf_expr.py @@ -60,7 +60,7 @@ def _eval_expr(cr, ident, workitem, action): def execute_action(cr, ident, workitem, activity): obj = pooler.get_pool(cr.dbname).get('ir.actions.server') - ctx = {'active_id':ident[2], 'active_ids':[ident[2]]} + ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]} result = obj.run(cr, ident[0], [activity['action_id']], ctx) return result From 52c5e2971031f0ada8497e631ac1cdb2f9e23d7e Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 16 Sep 2011 15:23:28 +0530 Subject: [PATCH 33/56] [FIX] edi_company: add default addess bzr revid: hmo@tinyerp.com-20110916095328-8wlmqjz4wjfrt0ir --- openerp/addons/base/res/edi_res_company.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/res/edi_res_company.py b/openerp/addons/base/res/edi_res_company.py index 0eabd39781e..945678c3259 100644 --- a/openerp/addons/base/res/edi_res_company.py +++ b/openerp/addons/base/res/edi_res_company.py @@ -85,8 +85,8 @@ class res_company(osv.osv): for company in records: edi_doc = {} company_address = False - res = partner_pool.address_get(cr, uid, [company.partner_id.id], ['contact', 'invoice']) - addr_id = res['invoice'] or res['contact'] + res = partner_pool.address_get(cr, uid, [company.partner_id.id], ['default', 'contact', 'invoice']) + addr_id = res['invoice'] or res['contact'] or res['default'] if addr_id: address = partner_address_pool.browse(cr, uid, addr_id, context=context) ctx = context.copy() From 63987af41eac0814801dfdb7bc97f6d7109472ab Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 16 Sep 2011 16:38:00 +0530 Subject: [PATCH 34/56] [FIX] edi: get partner already exits or not using get_object and get_object_by_name. bzr revid: hmo@tinyerp.com-20110916110800-8dec58lrgyilv144 --- openerp/addons/base/ir/ir_edi.py | 11 +++++---- openerp/addons/base/res/edi_res_company.py | 26 +++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 062517b1f87..e830e18428f 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -420,6 +420,12 @@ class edi(object): openobject = model.browse(cr, uid, data.res_id, context=context) return openobject + def edi_create_relation(self, cr, uid, relation_model, relation_value, context=None): + relation_model = self.pool.get(relation_model) + values = {} #relation_model.default_get(cr, uid, fields, context=context) + values[relation_model._rec_name] = relation_value + return relation_model.create(cr, uid, values, context=context) + def edi_import_relation(self, cr, uid, relation_model, relation_value, xml_id=None, context=None): relation = False if xml_id: @@ -427,10 +433,7 @@ class edi(object): if not relation: relation = self.edi_get_object_by_name(cr, uid, relation_value, relation_model, context=context) if not relation: - relation_model = self.pool.get(relation_model) - values = {} #relation_model.default_get(cr, uid, fields, context=context) - values[relation_model._rec_name] = relation_value - relation_id = relation_model.create(cr, uid, values, context=context) + relation_id = self.edi_create_relation(cr, uid, relation_model, relation_value, context=context) relation = relation_model.browse(cr, uid, relation_id, context=context) record_xml = self.record_xml_id(cr, uid, relation, context=context) diff --git a/openerp/addons/base/res/edi_res_company.py b/openerp/addons/base/res/edi_res_company.py index 945678c3259..53ddf487996 100644 --- a/openerp/addons/base/res/edi_res_company.py +++ b/openerp/addons/base/res/edi_res_company.py @@ -54,13 +54,29 @@ class res_company(osv.osv): """ if values is None: values = {} - partner_pool = self.pool.get('res.partner') + partner_model = 'res.partner' + partner_pool = self.pool.get(partner_model) + xml_id = edi_document['company_id'][0] + company_address = edi_document.get('company_address', False) + partner_name = edi_document['company_id'][1] + partner = partner_pool.edi_get_object(cr, uid, xml_id, partner_model, context=context) + if not partner: + partner = partner_pool.edi_get_object_by_name(cr, uid, partner_name, partner_model, context=context) + + if partner: + record_xml = partner_pool.record_xml_id(cr, uid, partner, context=context) + if record_xml: + module, xml_id = record_xml + xml_id = '%s.%s' % (module, xml_id) + edi_document_partner = { - '__model': 'res.partner', - '__id' : edi_document['company_id'][0], - 'name' : edi_document['company_id'][1], - 'address': [edi_document['company_address']] + '__model': partner_model, + '__id' : xml_id, + 'name' : partner_name, } + if company_address: + edi_document_partner['address'] = [company_address] + edi_document_partner.update(values) return partner_pool.edi_import(cr, uid, edi_document_partner, context=context) From 2e02eaf46fb3a0fa520ca52951c78bd6cb91beae Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 30 Sep 2011 14:19:00 +0200 Subject: [PATCH 35/56] [IMP] edi: review/cleanup (wip) bzr revid: odo@openerp.com-20110930121900-1fzq6b0gbhxxki0j --- openerp/addons/base/ir/ir_edi.py | 236 +++++++++---------- openerp/addons/base/res/res_partner_demo.xml | 9 + openerp/service/web_services.py | 1 - 3 files changed, 124 insertions(+), 122 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index e830e18428f..6efdd64d5a0 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2011 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -15,7 +15,7 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## @@ -44,108 +44,107 @@ def safe_unique_id(database_id, model, record_id): digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2])) # finally, use the b64-encoded folded digest as ID part of the unique ID: digest = base64.urlsafe_b64encode(digest) - return '%s-%s' % (model.replace('.','_'), digest) class ir_edi_document(osv.osv): _name = 'ir.edi.document' - _description = 'To represent the EDI Document of any OpenERP record.' + _description = 'EDI Document' _columns = { - 'name': fields.char("EDI token", size = 128, help="EDI Token is a unique identifier for the EDI document."), - 'document': fields.text("Document", help="hold the serialization of the EDI document.") - + 'name': fields.char("EDI token", size = 128, help="Unique identifier for retrieving an EDI document."), + 'document': fields.text("Document", help="EDI document content") } _sql_constraints = [ - ('name_uniq', 'unique (name)', 'The EDI Token must be unique!') + ('name_uniq', 'unique (name)', 'EDI Tokens must be unique!') ] - - + def new_edi_token(self, cr, uid, record): - """ - Return a new, random unique token to identify an edi.document - :param record: It's a object of browse_record of any model + """Return a new, random unique token to identify this model record, + and to be used as token when exporting it as an EDI document. + + :param browse_record record: model record for which a token is needed """ db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - edi_token = hashlib.sha256('%s-%s-%s-%s' % (time.time(), db_uuid, record._name, record.id)).hexdigest() return edi_token - + def serialize(self, edi_documents): - """Serialize the list of dictionaries using json dumps method - perform a JSON serialization of a list of dicts prepared by generate_edi() and return a UTF-8 encoded string that could be passed to deserialize() - :param edi_dicts: it's list of edi_dict + """Serialize the given EDI document structures (Python dicts holding EDI data), + using JSON serialization. + + :param [dict] edi_documents: list of EDI document structures to serialize + :return: UTF-8 encoded string containing the serialized document """ serialized_list = json.dumps(edi_documents) return serialized_list - + def generate_edi(self, cr, uid, records, context=None): + """Generates a final EDI document containing the EDI serialization + of the given records, which should all be instances of a Model + that has the :meth:`~.edi` mixin. The document is not saved in the + database, this is done by :meth:`~.export_edi`. + + :param list(browse_record) records: records to export as EDI + :return: UTF-8 encoded string containing the serialized records """ - Generate the list of dictionaries using edi_export method of edi class - :param records: it's a object of browse_record_list of any model - """ - edi_list = [] for record in records: record_model_obj = self.pool.get(record._name) edi_list += record_model_obj.edi_export(cr, uid, [record], context=context) return self.serialize(edi_list) - - def get_document(self, cr, uid, edi_token, context=None): - """ - Get the edi document from database using given edi token - returns the string serialization that is in the database (column: document) for the given edi_token or raise. - """ - - records = self.name_search(cr, uid, edi_token, operator='=', context=context) - if records: - record = records[0][0] - edi = self.browse(cr, uid, record, context=context) - return edi.document - else: - pass - - def load_edi(self, cr, uid, edi_documents, context=None): - """ - loads the values from list of dictionaries to the corresponding OpenERP records - using the edi_import method of edi class - For each edi record (dict) in the list, call the corresponding osv.edi_import() method, based on the __model attribute (see section 2.4 of for spec of - osv.edi_import) - :param edi_dicts: list of edi_dict + def get_document(self, cr, uid, edi_token, context=None): + """Retrieve the EDI document corresponding to the given edi_token. + + :return: EDI document string + :raise: ValueError if requested EDI token does not match any know document """ - module_obj =self.pool.get('ir.module.module') + edi_ids = self.search(cr, uid, [('name','=', edi_token)], context=context) + if not edi_ids: + raise ValueError('Invalid EDI token: %s' % edi_token) + edi = self.browse(cr, uid, edi_ids[0], context=context) + return edi.document + + def load_edi(self, cr, uid, edi_documents, context=None): + """Import the given EDI document structures into the system, using + :meth:`~.import_edi`. + + :param edi_documents: list of Python dicts containing the deserialized + version of EDI documents + :return: list of (model, id) pairs containing the model and database ID + of all records that were imported in the system + """ + ir_module = self.pool.get('ir.module.module') res = [] for edi_document in edi_documents: module = edi_document.get('__module') - module_ids = module_obj.search(cr, uid, [('name','=',module),('state','not in',['uninstalled', 'uninstallable', 'to remove'])]) - if not module_ids: - raise osv.except_osv(_('Invalid action !'), - _('The document you are trying to import requires the OpenERP "%s" application. The OpenERP configuration assistant will help with this if you are connected as an administrator.')%(module)) + if not ir_module.search(cr, uid, [('name','=',module),('state','=','installed')]): + raise osv.except_osv(_('Missing Application'), + _("The document you are trying to import requires the OpenERP `%s` application. " + "The OpenERP configuration assistant will help with this if you are connected as an administrator.")%(module,)) model = edi_document.get('__model') - assert model, _('model should be provided in EDI Dict') + assert model, '__model attribute is required in each EDI document' model_obj = self.pool.get(model) record_id = model_obj.edi_import(cr, uid, edi_document, context=context) - res.append((model,record_id)) + res.append((model, record_id)) return res - - def deserialize(self, edi_document_string): - """ Deserialized the edi document string - perform JSON deserialization from an edi document string, and returns a list of dicts - """ - edi_document = json.loads(edi_document_string) - - return edi_document - - def export_edi(self, cr, uid, records, context=None): - """ - The method handles the flow of the edi document generation and store it in - the database and return the edi_token of the particular document - Steps: - * call generate_edi() to get a serialization and new_edi_token() to get a unique ID - * serialize the list returned by generate_edi() using serialize(), and save it in database with unique ID. - * return the unique ID - : param records: list of browse_record of any model + def deserialize(self, edi_documents_string): + """Return deserialized version of the given EDI Document string. + + :param str|unicode edi_documents_string: UTF-8 string (or unicode) containing + JSON-serialized EDI document(s) + :return: Python object representing the EDI document(s) (usually a list of dicts) + """ + return json.loads(edi_documents_string) + + def export_edi(self, cr, uid, records, context=None): + """Export the given database records as EDI documents, stores them + permanently with a new unique EDI token, for later retrieval via :meth:`~.get_document`, + and returns the list of the new corresponding ``ir.edi.document`` records. + + :param records: list of browse_record of any model + :return: list of IDs of the new ``ir.edi.document`` entries, in the same + order as the provided ``records``. """ exported_ids = [] for record in records: @@ -155,40 +154,35 @@ class ir_edi_document(osv.osv): 'name': token, 'document': document }, context=context) - exported_ids.append(token) return exported_ids - + def import_edi(self, cr, uid, edi_document=None, edi_url=None, context=None): + """Import a JSON serialized EDI Document string into the system, first retrieving it + from the given ``edi_url`` if provided. + + :param str|unicode edi_document: UTF-8 string or unicode containing JSON-serialized + EDI Document to import. Must not be provided if + ``edi_url`` is given. + :param str|unicode edi_url: URL where the EDI document (same format as ``edi_document``) + may be retrieved, without authentication. """ - The method handles the flow of importing particular edi document and - updates the database values on the basis of the edi document using - edi_loads method - - * N: a serialized edi.document or the URL to download a serialized document - * If a URL is provided, download it first to get the document - * Calls deserialize() to get the resulting list of dicts from the document - * Call load_edi() with the list of dicts, to create or update the corresponding OpenERP records based on the edi.document. - """ - - if edi_url and not edi_document: + if edi_url: + assert not edi_document, 'edi_document must not be provided if edi_url is given' edi_document = urllib2.urlopen(edi_url).read() - assert edi_document, _('EDI Document should be provided') + assert edi_document, 'EDI Document is empty!' edi_documents = self.deserialize(edi_document) return self.load_edi(cr, uid, edi_documents, context=context) - -ir_edi_document() + class edi(object): - _name = 'edi' - _description = 'edi document handler' - - """Mixin class for OSV objects that want be exposed as EDI documents. - Classes that inherit from this mixin class should override the + """Mixin class for Model objects that want be exposed as EDI documents. + Classes that inherit from this mixin class should override the ``edi_import()`` and ``edi_export()`` methods to implement their - specific behavior, based on the primitives provided by this superclass.""" + specific behavior, based on the primitives provided by this mixin.""" def record_xml_id(self, cr, uid, record, context=None): + #TODO: remove this -> use ir.model.data directly model_data_pool = self.pool.get('ir.model.data') data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) if not data_ids: @@ -233,12 +227,12 @@ class edi(object): 'module': module, 'res_id': record.id}, context=context) return xml_id - + def edi_metadata(self, cr, uid, records, context=None): """Return a list representing the boilerplate EDI structure for exporting the record in the given browse_rec_list, including the metadata fields - + The metadata fields MUST always include: - __model': the OpenERP model name - __module': the OpenERP module containing the model @@ -270,8 +264,8 @@ class edi(object): 'content': base64.encodestring(attachment.datas), 'file_name': attachment.datas_fname, }) - - + + uuid = self.edi_xml_id(cr, uid, record, context=context) edi_dict = { '__id': uuid, @@ -285,7 +279,7 @@ class edi(object): '__attachments': attachment_dict_list }) edi_dict_list.append(edi_dict) - + return edi_dict_list def edi_m2o(self, cr, uid, record, context=None): @@ -296,11 +290,11 @@ class edi(object): """ # generic implementation! db_uuid = self.edi_xml_id(cr, uid, record, context=context) - relation_model_pool = self.pool.get(record._name) + relation_model_pool = self.pool.get(record._name) name = relation_model_pool.name_get(cr, uid, [record.id], context=context) name = name and name[0][1] or False return [db_uuid, name] - + def edi_o2m(self, cr, uid, records, edi_struct=None, context=None): """Return a list representing a O2M EDI value for the browse_records from the given browse_record_list. @@ -320,17 +314,17 @@ class edi(object): ctx = context.copy() ctx.update({'o2m_export':True}) for record in records: - + model_obj = self.pool.get(record._name) dict_list += model_obj.edi_export(cr, uid, [record], edi_struct=edi_struct, context=ctx) - + return dict_list - + def edi_m2m(self, cr, uid, records, context=None): """Return a list representing a M2M EDI value for the browse_records from the given browse_record_list. - Example: + Example: 'related_tasks': [ # M2M fields would exported as a list of pairs, ['db-uuid:xml-id1', # similar to a list of M2O values. 'Task 01: bla bla'], @@ -340,10 +334,10 @@ class edi(object): """ # generic implementation! dict_list = [] - + for record in records: dict_list.append(self.edi_m2o(cr, uid, record, context=None)) - + return dict_list def edi_export(self, cr, uid, records, edi_struct=None, context=None): @@ -366,7 +360,7 @@ class edi(object): be included in the exported data. """ # generic implementation! - + if context is None: context = {} if edi_struct is None: @@ -380,7 +374,7 @@ class edi(object): edi_dict.update(self.edi_metadata(cr, uid, [row], context=context)[0]) for field in fields_to_export: _column = _columns[field].column - _column_dict = fields.field_to_dict(self, cr, uid, context, _column) + _column_dict = fields.field_to_dict(self, cr, uid, _column, context=context) record = getattr(row, field) if not record: continue @@ -423,7 +417,7 @@ class edi(object): def edi_create_relation(self, cr, uid, relation_model, relation_value, context=None): relation_model = self.pool.get(relation_model) values = {} #relation_model.default_get(cr, uid, fields, context=context) - values[relation_model._rec_name] = relation_value + values[relation_model._rec_name] = relation_value return relation_model.create(cr, uid, values, context=context) def edi_import_relation(self, cr, uid, relation_model, relation_value, xml_id=None, context=None): @@ -442,13 +436,13 @@ class edi(object): xml_id = '%s.%s' % (module, xml_id) if not xml_id: xml_id = self.edi_xml_id(cr, uid, relation, context=context) - + #TODO: update record from values ? #relation_id = model_data._update(cr, uid, relation_model, module, values, xml_id=xml_id, context=context) return relation and relation.id or False - + def edi_import(self, cr, uid, edi_document, context=None): - + """Imports a list of dicts representing an edi.document, using the generic algorithm. @@ -492,7 +486,7 @@ class edi(object): For each pair in the M2M: a. Perform the same steps as for a Many2One (see 1.2.1.1) b. After finding the database ID of the final record in the database, - connect it to the parent record via a write value like (4, db_id). + connect it to the parent record via a write value like (4, db_id). """ # generic implementation! model_data = self.pool.get('ir.model.data') @@ -508,8 +502,8 @@ class edi(object): continue if not field.startswith('__'): _column = _columns[field].column - _column_dict = fields.field_to_dict(self, cr, uid, context, _column) - + _column_dict = fields.field_to_dict(self, cr, uid, _column, context=context) + edi_field_value = datas[field] field_type = _column_dict['type'] relation_model = _column_dict.get('relation') @@ -544,21 +538,21 @@ class edi(object): for edi_parent_document in edi_parent_documents: relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], edi_parent_document[0], context=context) parent_lines.append(relation_id) - - if len(parent_lines): + + if len(parent_lines): if field_type == 'many2one': values[field] = parent_lines[0] - + else: many2many_ids = [] for m2m_id in parent_lines: many2many_ids.append((4, m2m_id)) values[field] = many2many_ids - + else: values[field] = edi_field_value return module, xml_id2, values - + module, xml_id, data_values = process(edi_document, self._name) return model_data._update(cr, uid, self._name, module, data_values, xml_id=xml_id, context=context) # vim: ts=4 sts=4 sw=4 si et diff --git a/openerp/addons/base/res/res_partner_demo.xml b/openerp/addons/base/res/res_partner_demo.xml index c6616cd20bb..f106702cd61 100644 --- a/openerp/addons/base/res/res_partner_demo.xml +++ b/openerp/addons/base/res/res_partner_demo.xml @@ -277,6 +277,7 @@ Dubois sprl + http://www.dubois.be/ @@ -284,17 +285,20 @@ Eric Dubois + Fabien Dupont + Lucie Vonck + @@ -302,6 +306,7 @@ notsotiny.be + @@ -309,6 +314,7 @@ + @@ -317,6 +323,7 @@ 1 0 + vicking-direct.be @@ -326,6 +333,7 @@ 1 + woodywoodpecker.com @@ -334,6 +342,7 @@ + http://www.zerooneinc.com/ diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 7b14768fc9e..09112f884fd 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -87,7 +87,6 @@ class edi(netsvc.ExportService): def __init__(self, name="edi"): netsvc.ExportService.__init__(self, name) - self.joinGroup("web-services") def dispatch(self, method, auth, params): if method in ['import_edi_document', 'import_edi_url']: From 98c24848e5e855c175185881a00d272556bd1e4e Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 30 Sep 2011 23:05:49 +0200 Subject: [PATCH 36/56] [IMP] edi: missing access rights bzr revid: odo@openerp.com-20110930210549-wzct536me8wxvjyg --- openerp/addons/base/security/ir.model.access.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index 659938a73a3..56e6c1b2408 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -127,3 +127,5 @@ "access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0 "access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1 "access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0 +"access_ir_edi_all_read","access_ir_edi_all_read","model_ir_edi_document",,1,0,0,0 +"access_ir_edi_employee_create","access_ir_edi_employee_create","model_ir_edi_document","group_user",1,0,1,0 From 0453878cf3663a9f59346fca53f4f13dee156b50 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 30 Sep 2011 23:06:51 +0200 Subject: [PATCH 37/56] [IMP] ir.mail_server: build_email support for alternative body content bzr revid: odo@openerp.com-20110930210651-jkkskerjt1fb4s9p --- openerp/addons/base/ir/ir_mail_server.py | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 96fa9f52a88..8da0f34f0f9 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -236,15 +236,18 @@ class ir_mail_server(osv.osv): return connection def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, - attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None): + attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None, + body_alternative=None, subtype_alternative='plain'): """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it. :param string email_from: sender email address :param list email_to: list of recipient addresses (to be joined with commas) :param string subject: email subject (no pre-encoding/quoting necessary) - :param string body: email body, according to the ``subtype`` (by default, plaintext). + :param string body: email body, of the type ``subtype`` (by default, plaintext). If html subtype is used, the message will be automatically converted - to plaintext and wrapped in multipart/alternative. + to plaintext and wrapped in multipart/alternative, unless an explicit + ``body_alternative`` version is passed. + :param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative`` :param string reply_to: optional value of Reply-To header :param string object_id: optional tracking identifier, to be included in the message-id for recognizing replies. Suggested format for object-id is "res_id-model", @@ -252,6 +255,8 @@ class ir_mail_server(osv.osv): :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'), must match the format of the ``body`` parameter. Default is 'plain', making the content part of the mail "text/plain". + :param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain' + or 'html'). Default is 'plain'. :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string containing the bytes of the attachment :param list email_cc: optional list of string values for CC header (to be joined with commas) @@ -276,7 +281,7 @@ class ir_mail_server(osv.osv): if not body: body = u'' email_body_utf8 = ustr(body).encode('utf-8') - email_text_part = MIMEText(email_body_utf8 or '', _subtype=subtype, _charset='utf-8') + email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8') msg = MIMEMultipart() if not message_id: @@ -304,13 +309,21 @@ class ir_mail_server(osv.osv): for key, value in headers.iteritems(): msg[ustr(key).encode('utf-8')] = encode_header(value) - if html2text and subtype == 'html': - # Always provide alternative text body if possible. + if subtype == 'html' and not body_alternative and html2text: + # Always provide alternative text body ourselves if possible. text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8') alternative_part = MIMEMultipart(_subtype="alternative") alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain')) alternative_part.attach(email_text_part) msg.attach(alternative_part) + elif body_alternative: + # Include both alternatives, as specified, within a multipart/alternative part + alternative_part = MIMEMultipart(_subtype="alternative") + body_alternative_utf8 = ustr(body_alternative).encode('utf-8') + alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8') + alternative_part.attach(alternative_body_part) + alternative_part.attach(email_text_part) + msg.attach(alternative_part) else: msg.attach(email_text_part) From bdf846b53539167de26456a185a2c0bda42a7585 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 30 Sep 2011 23:07:44 +0200 Subject: [PATCH 38/56] [FIX] edi: ir.model.data creation should be done as super-user bzr revid: odo@openerp.com-20110930210744-7eb3ebwk5czfxbm5 --- openerp/addons/base/ir/ir_edi.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 6efdd64d5a0..6bc28f69778 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -19,15 +19,16 @@ # ############################################################################## -from osv import osv,fields +import base64 import hashlib import json import time -import base64 import urllib2 + +import openerp import openerp.release as release +from osv import osv,fields from tools.translate import _ -import netsvc def split_xml_id(xml_id): assert len(xml_id.split('.'))==2, \ @@ -117,7 +118,7 @@ class ir_edi_document(osv.osv): res = [] for edi_document in edi_documents: module = edi_document.get('__module') - if not ir_module.search(cr, uid, [('name','=',module),('state','=','installed')]): + if module != 'base' and not ir_module.search(cr, uid, [('name','=',module),('state','=','installed')]): raise osv.except_osv(_('Missing Application'), _("The document you are trying to import requires the OpenERP `%s` application. " "The OpenERP configuration assistant will help with this if you are connected as an administrator.")%(module,)) @@ -221,7 +222,7 @@ class edi(object): uuid = safe_unique_id(db_uuid, record._name, record.id) xml_id = '%s:%s.%s' % (db_uuid, record._module, uuid) module, xml_id2 = split_xml_id(xml_id) - xml_record_id = model_data_pool.create(cr, uid, { + xml_record_id = model_data_pool.create(cr, openerp.SUPERUSER_ID, { 'name': xml_id2, 'model': record._name, 'module': module, From 0489f83a445f5c50aa9f2901d88b9b5f8499607d Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Sat, 1 Oct 2011 00:23:55 +0200 Subject: [PATCH 39/56] [IMP] orm: rename/cleanup: get_xml_id* -> get_external_id* bzr revid: odo@openerp.com-20110930222355-jtuo4z9dz995emxk --- openerp/osv/orm.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2a8553e122b..246187a5424 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4591,17 +4591,21 @@ class BaseModel(object): return False return True - def _get_xml_ids(self, cr, uid, ids, *args, **kwargs): - """Find out the XML ID(s) of any database record. + def _get_external_ids(self, cr, uid, ids, *args, **kwargs): + """Retrieve the External ID(s) of any database record. **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }`` - :return: map of ids to the list of their fully qualified XML IDs - (empty list when there's none). + :return: map of ids to the list of their fully qualified External IDs + in the form ``module.key``, or an empty list when there's no External + ID for a record, e.g.:: + + { 'id': ['module.ext_id', 'module.ext_id_bis'], + 'id2': [] } """ - model_data_obj = self.pool.get('ir.model.data') - data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)]) - data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id']) + ir_model_data = self.pool.get('ir.model.data') + data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)]) + data_results = ir_model_data.read(cr, uid, data_ids, ['module', 'name', 'res_id']) result = {} for id in ids: # can't use dict.fromkeys() as the list would be shared! @@ -4610,29 +4614,35 @@ class BaseModel(object): result[record['res_id']].append('%(module)s.%(name)s' % record) return result - def get_xml_id(self, cr, uid, ids, *args, **kwargs): - """Find out the XML ID of any database record, if there + def get_external_id(self, cr, uid, ids, *args, **kwargs): + """Retrieve the External ID of any database record, if there is one. This method works as a possible implementation for a function field, to be able to add it to any - model object easily, referencing it as ``osv.osv.get_xml_id``. + model object easily, referencing it as ``Model.get_external_id``. - When multiple XML IDs exist for a record, only one + When multiple External IDs exist for a record, only one of them is returned (randomly). - **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }`` - :return: map of ids to their fully qualified XML ID, defaulting to an empty string when there's none - (to be usable as a function field). + (to be usable as a function field), + e.g.:: + + { 'id': 'module.ext_id', + 'id2': '' } """ results = self._get_xml_ids(cr, uid, ids) - for k, v in results.items(): + for k, v in results.iteritems(): if results[k]: results[k] = v[0] else: results[k] = '' return results + # backwards compatibility + get_xml_id = get_external_id + _get_xml_ids = _get_external_ids + # Transience def is_transient(self): """ Return whether the model is transient. From 288e8ebcfca60f77d439206528fa7cf1086e9682 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 3 Oct 2011 16:40:49 +0200 Subject: [PATCH 40/56] [IMP] browse_record._table -> browse_record._model bzr revid: odo@openerp.com-20111003144049-d6o4igae8mslns5x --- openerp/osv/orm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 246187a5424..5a28994460a 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -303,7 +303,8 @@ class browse_record(object): self._cr = cr self._uid = uid self._id = id - self._table = table + self._table = table # deprecated, use _model! + self._model = table self._table_name = self._table._name self.__logger = logging.getLogger( 'osv.browse_record.' + self._table_name) @@ -2853,9 +2854,11 @@ class BaseModel(object): def _auto_end(self, cr, context=None): """ Create the foreign keys recorded by _auto_init. """ + self.__logger.info('Establishing foreign key references...') for t, k, r, d in self._foreign_keys: cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d)) cr.commit() + self.__logger.info('Establishing foreign key references... done') del self._foreign_keys From ea019d556f341bf09f316bc7fb6409980dfd74cf Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 3 Oct 2011 19:13:02 +0200 Subject: [PATCH 41/56] [IMP] yaml_import: make !ref nodes work for m2o values bzr revid: odo@openerp.com-20111003171302-n1quzvs2pjyn7xr6 --- openerp/tools/yaml_import.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index bb4ce9db87d..91cdda1b896 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -340,6 +340,7 @@ class YamlInterpreter(object): return record_dict def process_ref(self, node, column=None): + assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute' if node.search: if node.model: model_name = node.model @@ -377,7 +378,10 @@ class YamlInterpreter(object): if column._type in ("many2many", "one2many"): value = [(6, 0, elements)] else: # many2one - value = self._get_first_result(elements) + if isinstance(elements, (list,tuple)): + value = self._get_first_result(elements) + else: + value = elements elif column._type == "many2one": value = self.get_id(expression) elif column._type == "one2many": From 3a224dfc06641c7caa892bc73fb67031a9ed514d Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 16:25:40 +0200 Subject: [PATCH 42/56] [IMP] res.partner: better context management in name_get/name_search bzr revid: odo@openerp.com-20111004142540-689vy1exk60druhy --- openerp/addons/base/res/res_partner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 1c136b6f30b..f8d1e6dd980 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -188,7 +188,7 @@ notifications, such as the availability of invoices."), def name_get(self, cr, uid, ids, context={}): if not len(ids): return [] - if context.get('show_ref', False): + if context and context.get('show_ref'): rec_name = 'ref' else: rec_name = 'name' @@ -199,8 +199,6 @@ notifications, such as the availability of invoices."), def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): if not args: args=[] - if not context: - context={} if name: ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context) if not ids: @@ -315,6 +313,8 @@ class res_partner_address(osv.osv): } def name_get(self, cr, user, ids, context={}): + if context is None: + context = {} if not len(ids): return [] res = [] From 4900560931fa934538f8443729e34c297fa2eb2f Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 16:29:51 +0200 Subject: [PATCH 43/56] [IMP] res.partner.edi: working edi implementation for res.partner + smoke test bzr revid: odo@openerp.com-20111004142951-klopyd7zn5sxf65g --- openerp/addons/base/res/edi_partner.py | 37 +++++++-- .../addons/base/test/test_edi_documents.yml | 80 +++++++++---------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/openerp/addons/base/res/edi_partner.py b/openerp/addons/base/res/edi_partner.py index 7a4127315f8..b8b64fad9e6 100644 --- a/openerp/addons/base/res/edi_partner.py +++ b/openerp/addons/base/res/edi_partner.py @@ -21,18 +21,45 @@ from osv import fields,osv from base.ir import ir_edi +RES_PARTNER_ADDRESS_EDI_STRUCT = { + 'name': True, + 'email': True, + 'street': True, + 'street2': True, + 'zip': True, + 'city': True, + 'country_id': True, + 'state_id': True, + 'phone': True, + 'fax': True, + 'mobile': True, +} + +RES_PARTNER_EDI_STRUCT = { + 'name': True, + 'ref': True, + 'lang': True, + 'website': True, + 'address': RES_PARTNER_ADDRESS_EDI_STRUCT +} + + class res_partner(osv.osv, ir_edi.edi): _inherit = "res.partner" - -res_partner() + + def edi_export(self, cr, uid, records, edi_struct=None, context=None): + return super(res_partner,self).edi_export(cr, uid, records, + dict(RES_PARTNER_EDI_STRUCT), + context=context) class res_partner_address(osv.osv, ir_edi.edi): _inherit = "res.partner.address" -res_partner_address() + def edi_export(self, cr, uid, records, edi_struct=None, context=None): + return super(res_partner_address,self).edi_export(cr, uid, records, + dict(RES_PARTNER_ADDRESS_EDI_STRUCT), + context=context) class res_partner_bank(osv.osv, ir_edi.edi): _inherit = "res.partner.bank" -res_partner_bank() - diff --git a/openerp/addons/base/test/test_edi_documents.yml b/openerp/addons/base/test/test_edi_documents.yml index 03dbe623ede..c9695af73e1 100644 --- a/openerp/addons/base/test/test_edi_documents.yml +++ b/openerp/addons/base/test/test_edi_documents.yml @@ -1,47 +1,43 @@ - - I create partner + In order to test the basic EDI system, I will export a partner, + modify the exported EDI document to add an attachment and change + the data, and then re-import it and re-export it. + + with an attached file, check the result, the alter the data + and reimport it. - - !record {model: res.partner, id: res_partner_test33}: - name: Ivan - title: base.res_partner_title_sir - website: www.openerp.com - address: - - type: default - name: Davidson - street: M.G. Road - zip: 354562 - city: Alhabad - phone: +91 985764653 - supplier: False - customer: True - company_id: base.main_company - -- - I am creating one Document to be attached in partner ". -- - !record {model: 'ir.attachment', id: document_4}: - name : "My partner document" - res_model : res.partner - type : binary - -- - I attach a pdf file in "My partner document" -- - !python {model: 'ir.attachment'}: | - import addons - import base64 - pdf_file = open(addons.get_module_resource('base','test', 'partner.pdf')) - self.write(cr, uid, [ref("document_4")], {'res_id':ref("res_partner_test33"), 'datas_fname': 'partner.pdf', 'datas' : base64.encodestring(pdf_file.read())}, context) - -- - I am Tesing of EDI functionality. so first export partner and than import it again. -- - !python {model: ir.edi.document}: | + import json partner_obj = self.pool.get('res.partner') - partners = [partner_obj.browse(cr, uid, ref("res_partner_test33"))] - tokens = self.export_edi(cr, uid, partners) - assert tokens, 'Token is not generated.' - document = self.get_document(cr, uid, tokens[0], context=context) - a = self.import_edi(cr, uid, edi_document = document) + tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_agrolait'))]) + doc = self.get_document(cr, uid, tokens[0], context=context) + edi_doc, = json.loads(doc) + # check content of the document + assert edi_doc.get('__id').endswith('.res_partner_agrolait'), 'Incorrect external ID' + assert edi_doc.get('__model') == 'res.partner', 'Incorrect/Missing __model' + assert edi_doc.get('__module') == 'base', 'Incorrect/Missing __module' + assert edi_doc.get('__last_update'), 'Missing __last_update' + + # try to import the document after changing the name and id, and attaching a + # file, and check that a new partner is returned + edi_doc['__id'] = 'base:xxd37f8a-xx55-11e0-xxdd-xx81124c8b50.res_partner_xxx' + edi_doc['name'] = 'AgroMilk' + attachment = { + 'name': 'Test file', + 'file_name': 'test.png', + 'content': 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAKCAYAAACE2W/HAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIDQ84BkjLWAYAAACNSURBVCjPjZKxDUJBDEOf/wbUbEHDhkxAzRjUDECBhMQYjIApuPBNpK+PpUinOPYll5Nt1iCJXifbSPpJZtHgXLUdu0FWpMEt87a/xtsmqjjNetPFyhuWYFuj7RcgQBNwXNGdw2CqY85yXWi5z7YBnmRyEHvg0YSXrLE9P30nwugOHHr+s5va4x+fofAGm1+JjnJICm0AAAAASUVORK5CYII=', + } + edi_doc['__attachments'] = [attachment] + doc = json.dumps([edi_doc]) + result, = self.import_edi(cr, uid, edi_document=doc) + assert result[0] == 'res.partner' and result[1] > ref('base.res_partner_agrolait'),\ + "Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_agrolait'), result) + + # export the same partner we just created, and see if the output matches the input + tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])]) + doc_output = self.get_document(cr, uid, tokens[0], context=context) + edi_doc_output, = json.loads(doc_output) + for attribute in ('__model', '__module', '__id', 'name', '__attachments'): + assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \ + 'Incorrect value for %s, expected %r, got %r' % (attribute, edi_doc.get(attribute), edi_doc_output.get(attribute)) \ No newline at end of file From 82a4a9de0a9ebcac4acc78e62c37ef72d573568e Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 16:30:50 +0200 Subject: [PATCH 44/56] [IMP] edi: review/corrected edi logic bzr revid: odo@openerp.com-20111004143050-wvpp2b9r81cho3m3 --- openerp/addons/base/ir/ir_edi.py | 676 ++++++++++++++++--------------- 1 file changed, 353 insertions(+), 323 deletions(-) diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py index 6bc28f69778..b9ae11fd71b 100644 --- a/openerp/addons/base/ir/ir_edi.py +++ b/openerp/addons/base/ir/ir_edi.py @@ -22,6 +22,8 @@ import base64 import hashlib import json +import logging +import re import time import urllib2 @@ -29,24 +31,45 @@ import openerp import openerp.release as release from osv import osv,fields from tools.translate import _ +from tools.parse_version import parse_version -def split_xml_id(xml_id): - assert len(xml_id.split('.'))==2, \ - _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id) - return xml_id.split('.') +EXTERNAL_ID_PATTERN = re.compile(r'^(\S+:)?(\S+?)\.(\S+)$') + +def split_external_id(ext_id): + match = EXTERNAL_ID_PATTERN.match(ext_id) + assert match, \ + _("'%s' is an invalid external ID") % (ext_id) + return {'module': match.group(1) and match.group(1)[:-1], + 'db_uuid': match.group(2), + 'id': match.group(3), + 'full': match.group(0)} def safe_unique_id(database_id, model, record_id): - """Generate a unique string to represent a (database id,model,record_id) pair - without being too long, without revealing the database id, and - with a very low probability of collisions. + """Generate a unique string to represent a (database_uuid,model,record_id) pair + without being too long, and with a very low probability of collisions. """ msg = "%s-%s-%s-%s" % (time.time(), database_id, model, record_id) digest = hashlib.sha1(msg).digest() + # fold the sha1 20 bytes digest to 9 bytes digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2])) - # finally, use the b64-encoded folded digest as ID part of the unique ID: + # b64-encode the 9-bytes folded digest to a reasonable 12 chars ASCII ID digest = base64.urlsafe_b64encode(digest) return '%s-%s' % (model.replace('.','_'), digest) +def version_tuple(): + return parse_version(release.version) + +def last_update_for(record): + """Returns the last update timestamp for the given record, + if available, otherwise False + """ + if record._model._log_access: + record_log = record.perm_read()[0] + return record_log.get('write_date') or record_log.get('create_date') or False + return False + +_logger = logging.getLogger('edi') + class ir_edi_document(osv.osv): _name = 'ir.edi.document' _description = 'EDI Document' @@ -99,6 +122,7 @@ class ir_edi_document(osv.osv): :return: EDI document string :raise: ValueError if requested EDI token does not match any know document """ + _logger.debug("get_document(%s)", edi_token) edi_ids = self.search(cr, uid, [('name','=', edi_token)], context=context) if not edi_ids: raise ValueError('Invalid EDI token: %s' % edi_token) @@ -134,7 +158,7 @@ class ir_edi_document(osv.osv): :param str|unicode edi_documents_string: UTF-8 string (or unicode) containing JSON-serialized EDI document(s) - :return: Python object representing the EDI document(s) (usually a list of dicts) + :return: Python object representing the EDI document(s) (usually a list of dicts) """ return json.loads(edi_documents_string) @@ -144,7 +168,7 @@ class ir_edi_document(osv.osv): and returns the list of the new corresponding ``ir.edi.document`` records. :param records: list of browse_record of any model - :return: list of IDs of the new ``ir.edi.document`` entries, in the same + :return: list of IDs of the new ``ir.edi.document`` entries, in the same order as the provided ``records``. """ exported_ids = [] @@ -163,7 +187,7 @@ class ir_edi_document(osv.osv): from the given ``edi_url`` if provided. :param str|unicode edi_document: UTF-8 string or unicode containing JSON-serialized - EDI Document to import. Must not be provided if + EDI Document to import. Must not be provided if ``edi_url`` is given. :param str|unicode edi_url: URL where the EDI document (same format as ``edi_document``) may be retrieved, without authentication. @@ -182,378 +206,384 @@ class edi(object): ``edi_import()`` and ``edi_export()`` methods to implement their specific behavior, based on the primitives provided by this mixin.""" - def record_xml_id(self, cr, uid, record, context=None): - #TODO: remove this -> use ir.model.data directly - model_data_pool = self.pool.get('ir.model.data') - data_ids = model_data_pool.search(cr, uid, [('res_id','=',record.id),('model','=',record._name)]) - if not data_ids: - return False - xml_record_id = data_ids[0] - xml_record = model_data_pool.browse(cr, uid, xml_record_id, context=context) - return xml_record.module, xml_record.name + # private method, not RPC-exposed as it creates ir.model.data entries as + # SUPERUSER based on its parameters + def _edi_external_id(self, cr, uid, record, existing_id=None, existing_module=None, + context=None): + """Generate/Retrieve unique external ID for ``record``. + Each EDI record and each relationship attribute in it is identified by a + unique external ID, which includes the database's UUID, as a way to + refer to any record within any OpenERP instance, without conflict. - def edi_xml_id(self, cr, uid, record, xml_id=None, context=None): - """ - Generate a unique string to represent a pair of (the database's UUID, the XML ID). - Each EDI record and each relationship value are represented using a unique - database identifier. These database identifiers include the database unique - ID, as a way to uniquely refer to any record within any OpenERP instance, - without conflict. - - For OpenERP records that have an existing "XML ID" (i.e. an entry in + For OpenERP records that have an existing "External ID" (i.e. an entry in ir.model.data), the EDI unique identifier for this record will be made of - "%s:%s" % (the database's UUID, the XML ID). The database's UUID MUST - NOT contain a colon characters (this is guaranteed by the UUID algorithm). + "%s:%s:%s" % (module, database UUID, ir.model.data ID). The database's + UUID MUST NOT contain a colon characters (this is guaranteed by the + UUID algorithm). - For OpenERP records that have no existing "XML ID", a new one should be - created during the EDI export. It is recommended that the generated XML ID + For records that have no existing ir.model.data entry, a new one will be + created during the EDI export. It is recommended that the generated external ID contains a readable reference to the record model, plus a unique value that - hides the database ID. - """ - model_data_pool = self.pool.get('ir.model.data') - db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') + hides the database ID. If ``existing_id`` is provided (because it came from + an import), it will be used instead of generating a new one. + If ``existing_module`` is provided (because it came from + an import), it will be used instead of using local values. - _record_xml = self.record_xml_id(cr, uid, record, context=context) - if _record_xml: - module, xml_id = _record_xml - xml_id = '%s:%s.%s' % (db_uuid, module, xml_id) + :param browse_record record: any browse_record needing an EDI external ID + :param string existing_id: optional existing external ID value, usually coming + from a just-imported EDI record, to be used instead + of generating a new one + :param string existing_module: optional existing module name, usually in the + format ``module:db_uuid`` and coming from a + just-imported EDI record, to be used instead + of local values + :return: the full unique External ID to use for record + """ + ir_model_data = self.pool.get('ir.model.data') + db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') + ext_id = self.get_external_id(cr, uid, [record.id])[record.id] + if not ext_id: + ext_id = existing_id or safe_unique_id(db_uuid, record._name, record.id) + # ID is unique cross-db thanks to db_uuid (already included in existing_module) + module = existing_module or "%s:%s" % (record._module, db_uuid) + _logger.debug("%s: Generating new external ID `%s.%s` for %r", self._name, + module, ext_id, record) + ir_model_data.create(cr, openerp.SUPERUSER_ID, + {'name': ext_id, + 'model': record._name, + 'module': module, + 'res_id': record.id}) else: - if not xml_id: - uuid = safe_unique_id(db_uuid, record._name, record.id) - xml_id = '%s:%s.%s' % (db_uuid, record._module, uuid) - module, xml_id2 = split_xml_id(xml_id) - xml_record_id = model_data_pool.create(cr, openerp.SUPERUSER_ID, { - 'name': xml_id2, - 'model': record._name, - 'module': module, - 'res_id': record.id}, context=context) - return xml_id + module, ext_id = ext_id.split('.') + if not ':' in module: + # this record was not previously EDI-imported + assert module == record._module, 'Module mismatch between record and its current'\ + 'external ID' + # ID is unique cross-db thanks to db_uuid + module = "%s:%s" % (module, db_uuid) + + return '%s.%s' % (module, ext_id) def edi_metadata(self, cr, uid, records, context=None): - """Return a list representing the boilerplate EDI structure for - exporting the record in the given browse_rec_list, including + """Return a list containing the boilerplate EDI structures for + exporting ``records`` as EDI, including the metadata fields - The metadata fields MUST always include: - - __model': the OpenERP model name - - __module': the OpenERP module containing the model - - __id': the unique (cross-DB) identifier for this record - - __last_update': last update date of record, ISO date string in UTC - - __version': a list of components for the version - - __attachments': a list (possibly empty) of dicts describing the files attached to this record. - """ - if context is None: - context = {} - data_ids = [] - attachment_object = self.pool.get('ir.attachment') - edi_dict_list = [] - db_uuid = '' - version = [] - for ver in release.major_version.split('.'): - try: - ver = int(ver) - except: - pass - version.append(ver) + The metadata fields always include:: + { + '__model': 'some.model', # record model + '__module': 'module', # require module + '__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! + '__version' : [6,1,0], # server version, to check compatibility. + '__attachments_': + } + + :param list(browse_record) records: records to export + :return: list of dicts containing boilerplate EDI metadata for each record, + at the corresponding index from ``records``. + """ + data_ids = [] + ir_attachment = self.pool.get('ir.attachment') + results = [] + version = version_tuple() for record in records: - attachment_ids = attachment_object.search(cr, uid, [('res_model','=', record._name), ('res_id', '=', record.id)]) - attachment_dict_list = [] - for attachment in attachment_object.browse(cr, uid, attachment_ids, context=context): - attachment_dict_list.append({ + 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': base64.encodestring(attachment.datas), + 'content': attachment.datas, # already base64 encoded! 'file_name': attachment.datas_fname, }) - - - uuid = self.edi_xml_id(cr, uid, record, context=context) + ext_id = self._edi_external_id(cr, uid, record, context=context) edi_dict = { - '__id': uuid, - '__last_update': False, #record.write_date, #TODO: convert into UTC + '__id': ext_id, + '__last_update': last_update_for(record), + '__model' : record._name, + '__module' : record._module, + '__version': version, + '__attachments': attachments, } - if not context.get('o2m_export'): - edi_dict.update({ - '__model' : record._name, - '__module' : record._module, - '__version': version, - '__attachments': attachment_dict_list - }) - edi_dict_list.append(edi_dict) - - return edi_dict_list + results.append(edi_dict) + return results def edi_m2o(self, cr, uid, record, context=None): - """Return a list representing a M2O EDI value for - the given browse_record. - M2O are passed as pair (ID, Name) - Exmaple: ['db-uuid:xml-id', 'Partner Name'] + """Return a m2o EDI representation for the given record. + + The EDI format for a many2one is:: + + ['unique_external_id', 'Document Name'] """ - # generic implementation! - db_uuid = self.edi_xml_id(cr, uid, record, context=context) - relation_model_pool = self.pool.get(record._name) - name = relation_model_pool.name_get(cr, uid, [record.id], context=context) + edi_ext_id = self._edi_external_id(cr, uid, record, context=context) + relation_model = record._model + name = relation_model.name_get(cr, uid, [record.id], context=context) name = name and name[0][1] or False - return [db_uuid, name] + return [edi_ext_id, name] def edi_o2m(self, cr, uid, records, edi_struct=None, context=None): - """Return a list representing a O2M EDI value for - the browse_records from the given browse_record_list. + """Return a list representing a O2M EDI relationship containing + all the given records, according to the given ``edi_struct``. + This is basically the same as exporting all the record using + :meth:`~.edi_export` with the given ``edi_struct``, and wrapping + the results in a list. - Example: - [ # O2M fields would be a list of dicts, with their - { '__id': 'db-uuid:xml-id', # own __id. - '__last_update': 'iso date', # The update date, just in case... - 'name': 'some name', - ... - }], + Example:: + + [ # O2M fields would be a list of dicts, with their + { '__id': 'module:db-uuid.id', # own __id. + '__last_update': 'iso date', # update date + 'name': 'some name', + #... + }, + # ... + ], """ - # generic implementation! - dict_list = [] - if context is None: - context = {} - ctx = context.copy() - ctx.update({'o2m_export':True}) + result = [] for record in records: - - model_obj = self.pool.get(record._name) - dict_list += model_obj.edi_export(cr, uid, [record], edi_struct=edi_struct, context=ctx) - - return dict_list + result += record._model.edi_export(cr, uid, [record], edi_struct=edi_struct, context=context) + return result def edi_m2m(self, cr, uid, records, context=None): - """Return a list representing a M2M EDI value for - the browse_records from the given browse_record_list. + """Return a list representing a M2M EDI relationship directed towards + all the given records. + This is basically the same as exporting all the record using + :meth:`~.edi_m2o` and wrapping the results in a list. - Example: - 'related_tasks': [ # M2M fields would exported as a list of pairs, - ['db-uuid:xml-id1', # similar to a list of M2O values. - 'Task 01: bla bla'], - ['db-uuid:xml-id2', - 'Task 02: bla bla'] - ] + Example:: + + # M2M fields are exported as a list of pairs, like a list of M2O values + [ + ['module:db-uuid.id1', 'Task 01: bla bla'], + ['module:db-uuid.id2', 'Task 02: bla bla'] + ] """ - # generic implementation! - dict_list = [] - - for record in records: - dict_list.append(self.edi_m2o(cr, uid, record, context=None)) - - return dict_list + return [self.edi_m2o(cr, uid, r, context=context) for r in records] def edi_export(self, cr, uid, records, edi_struct=None, context=None): """Returns a list of dicts representing an edi.document containing the - browse_records with ``ids``, using the generic algorithm. - :param edi_struct: if provided, edi_struct should be a dictionary - with a skeleton of the OSV fields to export as edi. - Basic fields can have any key as value, but o2m - values should have a sample skeleton dict as value. - For example, for a res.partner record: - edi_struct: { - 'name': True, - 'company_id': True, - 'address': { - 'name': True, - 'street': True, - } - } - Any field not specified in the edi_struct will not - be included in the exported data. - """ - # generic implementation! + records, and matching the given ``edi_struct``, if provided. - if context is None: - context = {} + :param edi_struct: if provided, edi_struct should be a dictionary + with a skeleton of the fields to export. + Basic fields can have any key as value, but o2m + values should have a sample skeleton dict as value, + to act like a recursive export. + For example, for a res.partner record:: + + edi_struct: { + 'name': True, + 'company_id': True, + 'address': { + 'name': True, + 'street': True, + } + } + + Any field not specified in the edi_struct will not + be included in the exported data. Fields with no + value (False) will be omitted in the EDI struct. + If edi_struct is omitted, no fields will be exported + """ if edi_struct is None: edi_struct = {} - _columns = self._all_columns - fields_to_export = edi_struct and edi_struct.keys() or _columns.keys() - edi_dict_list = [] - value = None - for row in records: - edi_dict = {} - edi_dict.update(self.edi_metadata(cr, uid, [row], context=context)[0]) + fields_to_export = edi_struct.keys() + results = [] + for record in records: + edi_dict = self.edi_metadata(cr, uid, [record], context=context)[0] for field in fields_to_export: - _column = _columns[field].column - _column_dict = fields.field_to_dict(self, cr, uid, _column, context=context) - record = getattr(row, field) - if not record: + column = self._all_columns[field].column + value = getattr(record, field) + if not value: continue #if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): # # Do not Export Function Fields and related fields # continue - elif _column_dict['type'] == 'many2one': - value = self.edi_m2o(cr, uid, record, context=context) - elif _column_dict['type'] == 'many2many': - value = self.edi_m2m(cr, uid, record, context=context) - elif _column_dict['type'] == 'one2many': - value = self.edi_o2m(cr, uid, record, edi_struct=edi_struct.get(field, {}), context=context ) - else: - value = record + elif column._type == 'many2one': + value = self.edi_m2o(cr, uid, value, context=context) + elif column._type == 'many2many': + value = self.edi_m2m(cr, uid, value, context=context) + elif column._type == 'one2many': + value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field, {}), context=context) edi_dict[field] = value - edi_dict_list.append(edi_dict) - return edi_dict_list + results.append(edi_dict) + return results - - def edi_get_object_by_name(self, cr, uid, value, model_name, context=None): - openobject = False + def _edi_get_object_by_name(self, cr, uid, name, model_name, context=None): model = self.pool.get(model_name) - object_ids = model.name_search(cr, uid, value, operator='=', context=context) - if object_ids and len(object_ids) == 1: - object_id = object_ids[0][0] - openobject = model.browse(cr, uid, object_id, context=context) - return openobject + search_results = model.name_search(cr, uid, name, operator='=', context=context) + if len(search_results) == 1: + return model.browse(cr, uid, search_results[0][0], context=context) + return False - def edi_get_object(self, cr, uid, xml_id, model, context=None): - model_data = self.pool.get('ir.model.data') - module, xml_id2 = split_xml_id(xml_id) - openobject = False - data_ids = model_data.search(cr, uid, [('model','=', model), ('name','=', xml_id2)]) + def _edi_import_attachments(self, cr, uid, record_id, edi_document, context=None): + ir_attachment = self.pool.get('ir.attachment') + for attachment in edi_document.get('__attachments', []): + # check attachment data is non-empty and valid + file_data = None + try: + file_data = base64.b64decode(attachment.get('content')) + except TypeError: + pass + assert file_data, 'Incorrect/Missing attachment file content' + assert attachment.get('name'), 'Incorrect/Missing attachment name' + assert attachment.get('file_name'), 'Incorrect/Missing attachment file name' + assert attachment.get('file_name'), 'Incorrect/Missing attachment file name' + ir_attachment.create(cr, uid, {'name': attachment['name'], + 'datas_fname': attachment['file_name'], + 'res_model': self._name, + 'res_id': record_id, + # should be pure 7bit ASCII + 'datas': str(attachment['content']), + }, context=context) + + + def _edi_get_object_by_external_id(self, cr, uid, external_id, model, context=None): + """Returns browse_record representing object identified by the model and external_id, + or None if no record was found with this external id. + + :param external_id: fully qualified external id, in the EDI form + ``module:db_uuid:identifier``. + :param model: model name the record belongs to. + """ + ir_model_data = self.pool.get('ir.model.data') + # external_id is expected to have the form: ``module:db_uuid:model.random_name`` + ext_id_members = split_external_id(external_id) + db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') + module = ext_id_members['module'] + ext_id = ext_id_members['id'] + ext_module = '%s:%s' % (module, ext_id_members['db_uuid']) + data_ids = ir_model_data.search(cr, uid, [('model','=',model), + ('name','=',ext_id), + ('module','in',[ext_module,module])]) if data_ids: model = self.pool.get(model) - data = model_data.browse(cr, uid, data_ids[0], context=context) - openobject = model.browse(cr, uid, data.res_id, context=context) - return openobject + data = ir_model_data.browse(cr, uid, data_ids[0], context=context) + result = model.browse(cr, uid, data.res_id, context=context) + return result - def edi_create_relation(self, cr, uid, relation_model, relation_value, context=None): - relation_model = self.pool.get(relation_model) - values = {} #relation_model.default_get(cr, uid, fields, context=context) - values[relation_model._rec_name] = relation_value - return relation_model.create(cr, uid, values, context=context) + def edi_import_relation(self, cr, uid, model, value, external_id, context=None): + """Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the + given model, returning the corresponding database ID: - def edi_import_relation(self, cr, uid, relation_model, relation_value, xml_id=None, context=None): - relation = False - if xml_id: - relation = self.edi_get_object(cr, uid, xml_id, relation_model, context=context) - if not relation: - relation = self.edi_get_object_by_name(cr, uid, relation_value, relation_model, context=context) - if not relation: - relation_id = self.edi_create_relation(cr, uid, relation_model, relation_value, context=context) - relation = relation_model.browse(cr, uid, relation_id, context=context) - - record_xml = self.record_xml_id(cr, uid, relation, context=context) - if record_xml: - module, xml_id = record_xml - xml_id = '%s.%s' % (module, xml_id) - if not xml_id: - xml_id = self.edi_xml_id(cr, uid, relation, context=context) - - #TODO: update record from values ? - #relation_id = model_data._update(cr, uid, relation_model, module, values, xml_id=xml_id, context=context) - return relation and relation.id or False + * First, checks if the ``external_id`` is already known, in which case the corresponding + database ID is directly returned, without doing anything else; + * If the ``external_id`` is unknown, attempts to locate an existing record + with the same ``value`` via name_search(). If found, the given external_id will + be assigned to this local record (in addition to any existing one) + * If previous steps gave no result, create a new record with the given + value in the target model, assign it the given external_id, and return + the new database ID + """ + _logger.debug("%s: Importing EDI relationship [%r,%r]", self._name, external_id, value) + target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context) + need_new_ext_id = False + if not target: + _logger.debug("%s: Importing EDI relationship [%r,%r] - ID not found, trying name_get", + self._name, external_id, value) + target = self._edi_get_object_by_name(cr, uid, value, model, context=context) + need_new_ext_id = True + if not target: + _logger.debug("%s: Importing EDI relationship [%r,%r] - name not found, creating it!", + self._name, external_id, value) + # also need_new_ext_id here, but already been set above + res_id, name = self.name_create(cr, uid, value, context=context) + target = model.browse(cr, uid, res_id, context=context) + if need_new_ext_id: + ext_id_members = split_external_id(external_id) + # module name is never used bare when creating ir.model.data entries, in order + # to avoid being taken as part of the module's data, and cleanup up at next update + module = "%s:%s" % (ext_id_members['module'], ext_id_members['db_uuid']) + # create a new ir.model.data entry for this value + self._edi_external_id(cr, uid, target, existing_id=ext_id_members['id'], existing_module=module, context=context) + return target.id def edi_import(self, cr, uid, edi_document, context=None): + """Imports a dict representing an edi.document into the system, + applying the following rules. - """Imports a list of dicts representing an edi.document, using the - generic algorithm. + All relationship fields are exported in a special way, and provide their own + unique identifier, so that we can avoid duplication of records when importing. - All relationship fields are exported in a special way, and provide their own - unique identifier, so that we can avoid duplication of records when importing. - Note: for all ir.model.data entries, the "module" value to use for read/write - should always be "edi_import", and the "name" value should be the full - db_id provided in the EDI. + #. Many2One + See :meth:`~.edi_import_relation` - 1: Many2One - M2O fields are always exported as a pair [db_id, name], where db_id - is in the form "db_uuid:xml_id", both being values that come directly - from the original database. - The import should behave like this: - a. Look in ir.model.data for a record that matches the db_id. - If found, replace the m2o value with the correct database ID and stop. - If not found, continue to next step. - b. Perform name_search(name) to look for a record that matches the - given m2o name. If only one record is found, create the missing - ir.model.data record to link it to the db_id, and the replace the m2o - value with the correct database ID, then stop. If zero result or - multiple results are found, go to next step. - c. Create the new record using the only field value that is known: the - name, and create the ir.model.data entry to map to it. - This should work for many models, and if not, the module should - provide a custom EDI import logic to care for it. + #. 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. - 2: 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. - a. 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 - using a write value like (4,db_id). - b. If not found via db_id, create a new entry using the same method that - imports a full EDI record (recursive call!), grab the resulting db id, - and use it to connect to the parent via a write value like (4, db_id). - - 3: Many2Many - M2M fields are always exported as a list of pairs similar to M2O. - For each pair in the M2M: - a. Perform the same steps as for a Many2One (see 1.2.1.1) - b. After finding the database ID of the final record in the database, - connect it to the parent record via a write value like (4, db_id). + #: 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. """ - # generic implementation! - model_data = self.pool.get('ir.model.data') - assert self._name == edi_document['__model'], 'EDI Document could not import. Model of EDI Document and current model are does not match' - def process(datas, model_name): - values = {} - model_pool = self.pool.get(model_name) - xml_id = datas['__id'] - module, xml_id2 = split_xml_id(xml_id) - _columns = model_pool._all_columns - for field in datas.keys(): - if field not in _columns: - continue - if not field.startswith('__'): - _column = _columns[field].column - _column_dict = fields.field_to_dict(self, cr, uid, _column, context=context) + assert self._name == edi_document['__model'], "EDI Document Model and current model do not match: "\ + "'%s' (EDI) vs '%s' (current)" % (edi_document['__model'], + self._name) - edi_field_value = datas[field] - field_type = _column_dict['type'] - relation_model = _column_dict.get('relation') - if not edi_field_value: - continue - if _column_dict.has_key('function') or _column_dict.has_key('related_columns'): - # DO NOT IMPORT FUNCTION FIELD AND RELATED FIELD - continue - elif field_type == 'one2many': - # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})] - relations = [] - relation_object = self.pool.get(relation_model) - for edi_relation_document in edi_field_value: - relation = self.edi_get_object(cr, uid, edi_relation_document['__id'], relation_model, context=context) - if not relation and edi_relation_document.get('name'): - relation = self.edi_get_object_by_name(cr, uid, edi_relation_document['name'], relation_model, context=context) - if relation: - self.edi_xml_id(cr, uid, relation, \ - xml_id=edi_relation_document['__id'], context=context) - relations.append((4, relation.id)) - res_module, res_xml_id, newrow = process(edi_relation_document, relation_model) - relations.append( (relation and 1 or 0, relation and relation.id or 0, newrow)) - values[field] = relations - elif field_type in ('many2one', 'many2many'): - if field_type == 'many2one': - edi_parent_documents = [edi_field_value] - else: - edi_parent_documents = edi_field_value + # 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']) + existing_id = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context) + if existing_id: + _logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full']) + return - parent_lines = [] + record_values = {} + o2m_todo = {} # o2m values are processed after their parent already exists + for field_name, field_value in edi_document.iteritems(): + # skip metadata and empty fields + if field_name.startswith('__') or field_value is None or field_value is False: + continue + field = self._all_columns[field_name].column + # skip function/related fields + if isinstance(field, fields.function): + _logger.warning("Unexpected function field value found in '%s' EDI document: '%s'" % (self._name, field_name)) + continue + relation_model = field._obj + if field._type == 'many2one': + record_values[field_name] = self.edi_import_relation(cr, uid, relation_model, + field_value[1], field_value[0], + context=context) + elif field._type == 'many2many': + record_values[field_name] = [self.edi_import_relation(cr, uid, relation_model, m2m_value[1], + m2m_value[0], context=context) + for m2m_value in field_value] + elif field._type == 'one2many': + # must wait until parent report is imported, as the parent relationship + # is often required in o2m child records + o2m_todo[field_name] = field_value + else: + record_values[field_name] = field_value - for edi_parent_document in edi_parent_documents: - relation_id = self.edi_import_relation(cr, uid, relation_model, edi_parent_document[1], edi_parent_document[0], context=context) - parent_lines.append(relation_id) + module_ref = "%s:%s" % (ext_id_members['module'], ext_id_members['db_uuid']) + record_id = self.pool.get('ir.model.data')._update(cr, uid, self._name, module_ref, record_values, + xml_id=ext_id_members['id'], context=context) - if len(parent_lines): - if field_type == 'many2one': - values[field] = parent_lines[0] + record_display, = self.name_get(cr, uid, [record_id], context=context) - else: - many2many_ids = [] - for m2m_id in parent_lines: - many2many_ids.append((4, m2m_id)) - values[field] = many2many_ids + # process o2m values, connecting them to their parent on-the-fly + for o2m_field, o2m_value in o2m_todo.iteritems(): + field = self._all_columns[o2m_field].column + dest_model = self.pool.get(field._obj) + for o2m_line in o2m_value: + # link to parent record: expects an (ext_id, name) pair + o2m_line[field._fields_id] = (ext_id_members['full'], record_display[1]) + dest_model.edi_import(cr, uid, o2m_line, context=context) - else: - values[field] = edi_field_value - return module, xml_id2, values + # process the attachments, if any + self._edi_import_attachments(cr, uid, record_id, edi_document, context=context) - module, xml_id, data_values = process(edi_document, self._name) - return model_data._update(cr, uid, self._name, module, data_values, xml_id=xml_id, context=context) -# vim: ts=4 sts=4 sw=4 si et + return record_id + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file From f07e1dc5586fd42656a1e2a57afbe2cfc7cbabd3 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 16:38:00 +0200 Subject: [PATCH 45/56] [IMP] res.company: temporarily comment out res.company edi helpers, needs review bzr revid: odo@openerp.com-20111004143800-lzca5kp7hng2djee --- openerp/addons/base/res/edi_res_company.py | 196 +++++++++++---------- 1 file changed, 100 insertions(+), 96 deletions(-) diff --git a/openerp/addons/base/res/edi_res_company.py b/openerp/addons/base/res/edi_res_company.py index 53ddf487996..3cf236756a4 100644 --- a/openerp/addons/base/res/edi_res_company.py +++ b/openerp/addons/base/res/edi_res_company.py @@ -19,101 +19,105 @@ # ############################################################################## -from osv import osv -from osv import fields -from tools.translate import _ +#from osv import osv +#from osv import fields +#from tools.translate import _ -class res_company(osv.osv): - _inherit = "res.company" +# TODO: CHECK below, seems useless +# +#class res_company(osv.osv): +# """Helper subclass for res.company providing util methods for working with +# companies in the context of EDI import/export. The res.company object +# itself is not EDI-exportable""" +# _inherit = "res.company" +# +# def edi_import_as_partner(self, cr, uid, edi_document, values=None, context=None): +# """ +# import company as a new partner +# company_address data used to add address to new partner +# +# edi_document is a dict to have company datas +# edi_document = { +# 'company_address': { +# 'street': True, +# 'street2': True, +# 'zip': True, +# 'city': True, +# 'state_id': True, +# 'country_id': True, +# 'email': True, +# 'phone': True, +# +# }, +# 'company_id': True, +# } +# values is a dict to have other datas of partner which are need to import of partner record +# values = { +# 'customer': True, +# 'supplier': True, +# } +# """ +# if values is None: +# values = {} +# partner_model = 'res.partner' +# partner_pool = self.pool.get(partner_model) +# xml_id = edi_document['company_id'][0] +# company_address = edi_document.get('company_address', False) +# partner_name = edi_document['company_id'][1] +# partner = partner_pool.edi_get_object(cr, uid, xml_id, partner_model, context=context) +# if not partner: +# partner = partner_pool.edi_get_object_by_name(cr, uid, partner_name, partner_model, context=context) +# +# if partner: + #FIXME +# record_xml = partner_pool._get_external_id(cr, uid, [partner.id], context=context) +# if record_xml: +# module, xml_id = record_xml +# xml_id = '%s.%s' % (module, xml_id) +# +# edi_document_partner = { +# '__model': partner_model, +# '__id' : xml_id, +# 'name' : partner_name, +# } +# if company_address: +# edi_document_partner['address'] = [company_address] +# +# edi_document_partner.update(values) +# return partner_pool.edi_import(cr, uid, edi_document_partner, context=context) +# +# def edi_export_address(self, cr, uid, records, edi_address_struct=None, context=None): +# """Return a dict representation of the address of each company record, suitable for +# inclusion in an EDI document, and matching the given edi_address_struct if provided. +# +# :param list(browse_record) records: list of companies to export +# :rtype: list(dict) +# :return: list of dicts, where each dict contains the address representation for +# the company record as the same index in ``records``. +# """ +# if context is None: +# context = {} +# res_partner = self.pool.get('res.partner') +# res_partner_address = self.pool.get('res.partner.address') +# if edi_address_struct is None: +# edi_address_struct = { +# 'street': True, +# 'street2': True, +# 'zip': True, +# 'city': True, +# 'state_id': True, +# 'country_id': True, +# 'email': True, +# 'phone': True, +# } +# results = [] +# for company in records: +# res = res_partner.address_get(cr, uid, [company.partner_id.id], ['default', 'contact', 'invoice']) +# addr_id = res['invoice'] or res['contact'] or res['default'] +# result = {} +# if addr_id: +# address = res_partner_address.browse(cr, uid, addr_id, context=context) +# result = res_partner_address.edi_export(cr, uid, [address], edi_struct=edi_address_struct, context=ctx)[0] +# results.append(result) +# return results - def edi_import_as_partner(self, cr, uid, edi_document, values=None, context=None): - """ - import company as a new partner - company_address data used to add address to new partner - - edi_document is a dict to have company datas - edi_document = { - 'company_address': { - 'street': True, - 'street2': True, - 'zip': True, - 'city': True, - 'state_id': True, - 'country_id': True, - 'email': True, - 'phone': True, - - }, - 'company_id': True, - } - values is a dict to have other datas of partner which are need to import of partner record - values = { - 'customer': True, - 'supplier': True, - } - """ - if values is None: - values = {} - partner_model = 'res.partner' - partner_pool = self.pool.get(partner_model) - xml_id = edi_document['company_id'][0] - company_address = edi_document.get('company_address', False) - partner_name = edi_document['company_id'][1] - partner = partner_pool.edi_get_object(cr, uid, xml_id, partner_model, context=context) - if not partner: - partner = partner_pool.edi_get_object_by_name(cr, uid, partner_name, partner_model, context=context) - - if partner: - record_xml = partner_pool.record_xml_id(cr, uid, partner, context=context) - if record_xml: - module, xml_id = record_xml - xml_id = '%s.%s' % (module, xml_id) - - edi_document_partner = { - '__model': partner_model, - '__id' : xml_id, - 'name' : partner_name, - } - if company_address: - edi_document_partner['address'] = [company_address] - - edi_document_partner.update(values) - return partner_pool.edi_import(cr, uid, edi_document_partner, context=context) - - def edi_export_address(self, cr, uid, records, edi_address_struct=None, context=None): - if context is None: - context = {} - partner_pool = self.pool.get('res.partner') - partner_address_pool = self.pool.get('res.partner.address') - if edi_address_struct is None: - edi_address_struct = { - 'street': True, - 'street2': True, - 'zip': True, - 'city': True, - 'state_id': True, - 'country_id': True, - 'email': True, - 'phone': True, - - } - edi_company_dict = {} - for company in records: - edi_doc = {} - company_address = False - res = partner_pool.address_get(cr, uid, [company.partner_id.id], ['default', 'contact', 'invoice']) - addr_id = res['invoice'] or res['contact'] or res['default'] - if addr_id: - address = partner_address_pool.browse(cr, uid, addr_id, context=context) - ctx = context.copy() - ctx.update({'o2m_export':True}) - edi_address_dict_list = partner_address_pool.edi_export(cr, uid, [address], edi_struct=edi_address_struct, context=ctx) - if edi_address_dict_list: - company_address = edi_address_dict_list[0] - edi_doc.update({ - 'company_address': company_address, - #'company_logo': company.logo,#TODO - }) - edi_company_dict[company.id] = edi_doc - return edi_company_dict -res_company() From 51b1201daae1ce17578f1635ef3328fd666c8dc6 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 22:47:05 +0200 Subject: [PATCH 46/56] [IMP] orm: remember Model._original_module when constructing inheritance ORM Models already have a _module attribute that contains the name of the module that declared this class, however sometimes we also need the name of the module that declared this model the first time. This will be stored in _original_module and is the name of the module to which the first parent with the same _name belongs to. bzr revid: odo@openerp.com-20111004204705-8z9o70n1ynpvng3i --- openerp/osv/orm.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 5a28994460a..2464a73ede4 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -816,13 +816,16 @@ class BaseModel(object): raise TypeError('_name is mandatory in case of multiple inheritance') for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): - parent_class = pool.get(parent_name).__class__ - if not pool.get(parent_name): + parent_model = pool.get(parent_name) + if not getattr(cls, '_original_module', None) and name == parent_model._name: + cls._original_module = parent_model._module + if not parent_model: raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n' 'You may need to add a dependency on the parent class\' module.' % (name, parent_name)) + parent_class = parent_model.__class__ nattr = {} for s in attributes: - new = copy.copy(getattr(pool.get(parent_name), s, {})) + new = copy.copy(getattr(parent_model, s, {})) if s == '_columns': # Don't _inherit custom fields. for c in new.keys(): @@ -850,6 +853,8 @@ class BaseModel(object): new.extend(cls.__dict__.get(s, [])) nattr[s] = new cls = type(name, (cls, parent_class), dict(nattr, _register=False)) + if not getattr(cls, '_original_module', None): + cls._original_module = cls._module obj = object.__new__(cls) obj.__init__(pool, cr) return obj From edf68f510cc62a82632f8d13b644e7a7cf63d683 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 22:48:00 +0200 Subject: [PATCH 47/56] [REM] orm: annoying INFO log lines bzr revid: odo@openerp.com-20111004204800-f4470vy0ou3ugxnm --- openerp/osv/orm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2464a73ede4..c66fc2a0d19 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2859,11 +2859,9 @@ class BaseModel(object): def _auto_end(self, cr, context=None): """ Create the foreign keys recorded by _auto_init. """ - self.__logger.info('Establishing foreign key references...') for t, k, r, d in self._foreign_keys: cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d)) cr.commit() - self.__logger.info('Establishing foreign key references... done') del self._foreign_keys From bfdbbb51cb4731808b6465ffdc5bf55d395dc162 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 22:51:40 +0200 Subject: [PATCH 48/56] [FIX] orm.export_data: 'module' column is required when creating external IDs The 'module' field of ir.model.data is required, so we we need to set it when auto-generating ir.mode.data entries. This acts as the namespace of the record. Because we don't want exported records to look like they belong to an existing module (and risk being garbage collected at the next module update), we put these auto-generated names in a reserved '__export__' module namespace. bzr revid: odo@openerp.com-20111004205140-duaww77ng4qmktj2 --- openerp/osv/orm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index c66fc2a0d19..9885c1a9697 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1053,6 +1053,7 @@ class BaseModel(object): 'name': n, 'model': self._name, 'res_id': r['id'], + 'module': '__export__', }) r = n else: From de6b69f654c88dbb837edefa002b8c3815df2ecf Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Oct 2011 22:52:54 +0200 Subject: [PATCH 49/56] [REM] edi: moved edi to a separate addon bzr revid: odo@openerp.com-20111004205254-82krwzabg0wuf2ka --- openerp/addons/base/__openerp__.py | 1 - openerp/addons/base/ir/__init__.py | 1 - openerp/addons/base/ir/ir_edi.py | 589 ------------------ openerp/addons/base/res/__init__.py | 2 - openerp/addons/base/res/edi_partner.py | 65 -- openerp/addons/base/res/edi_res_company.py | 123 ---- .../addons/base/security/ir.model.access.csv | 2 - .../addons/base/test/test_edi_documents.yml | 43 -- openerp/service/web_services.py | 63 -- 9 files changed, 889 deletions(-) delete mode 100644 openerp/addons/base/ir/ir_edi.py delete mode 100644 openerp/addons/base/res/edi_partner.py delete mode 100644 openerp/addons/base/res/edi_res_company.py delete mode 100644 openerp/addons/base/test/test_edi_documents.yml diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index 5233cdcbfdb..e436a0a6005 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -92,7 +92,6 @@ 'test/test_osv_expression.yml', 'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules. 'test/test_ir_values.yml', - 'test/test_edi_documents.yml', # Commented because this takes some time. # This must be (un)commented with the corresponding import statement # in test/__init__.py. diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index 850a3ae40de..4a6bd3c614c 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -36,7 +36,6 @@ import ir_rule import wizard import ir_config_parameter import osv_memory_autovacuum -import ir_edi import ir_mail_server # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/ir/ir_edi.py b/openerp/addons/base/ir/ir_edi.py deleted file mode 100644 index b9ae11fd71b..00000000000 --- a/openerp/addons/base/ir/ir_edi.py +++ /dev/null @@ -1,589 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2011 OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import base64 -import hashlib -import json -import logging -import re -import time -import urllib2 - -import openerp -import openerp.release as release -from osv import osv,fields -from tools.translate import _ -from tools.parse_version import parse_version - -EXTERNAL_ID_PATTERN = re.compile(r'^(\S+:)?(\S+?)\.(\S+)$') - -def split_external_id(ext_id): - match = EXTERNAL_ID_PATTERN.match(ext_id) - assert match, \ - _("'%s' is an invalid external ID") % (ext_id) - return {'module': match.group(1) and match.group(1)[:-1], - 'db_uuid': match.group(2), - 'id': match.group(3), - 'full': match.group(0)} - -def safe_unique_id(database_id, model, record_id): - """Generate a unique string to represent a (database_uuid,model,record_id) pair - without being too long, and with a very low probability of collisions. - """ - msg = "%s-%s-%s-%s" % (time.time(), database_id, model, record_id) - digest = hashlib.sha1(msg).digest() - # fold the sha1 20 bytes digest to 9 bytes - digest = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in zip(digest[:9], digest[9:-2])) - # b64-encode the 9-bytes folded digest to a reasonable 12 chars ASCII ID - digest = base64.urlsafe_b64encode(digest) - return '%s-%s' % (model.replace('.','_'), digest) - -def version_tuple(): - return parse_version(release.version) - -def last_update_for(record): - """Returns the last update timestamp for the given record, - if available, otherwise False - """ - if record._model._log_access: - record_log = record.perm_read()[0] - return record_log.get('write_date') or record_log.get('create_date') or False - return False - -_logger = logging.getLogger('edi') - -class ir_edi_document(osv.osv): - _name = 'ir.edi.document' - _description = 'EDI Document' - _columns = { - 'name': fields.char("EDI token", size = 128, help="Unique identifier for retrieving an EDI document."), - 'document': fields.text("Document", help="EDI document content") - } - _sql_constraints = [ - ('name_uniq', 'unique (name)', 'EDI Tokens must be unique!') - ] - - def new_edi_token(self, cr, uid, record): - """Return a new, random unique token to identify this model record, - and to be used as token when exporting it as an EDI document. - - :param browse_record record: model record for which a token is needed - """ - db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - edi_token = hashlib.sha256('%s-%s-%s-%s' % (time.time(), db_uuid, record._name, record.id)).hexdigest() - return edi_token - - def serialize(self, edi_documents): - """Serialize the given EDI document structures (Python dicts holding EDI data), - using JSON serialization. - - :param [dict] edi_documents: list of EDI document structures to serialize - :return: UTF-8 encoded string containing the serialized document - """ - serialized_list = json.dumps(edi_documents) - return serialized_list - - def generate_edi(self, cr, uid, records, context=None): - """Generates a final EDI document containing the EDI serialization - of the given records, which should all be instances of a Model - that has the :meth:`~.edi` mixin. The document is not saved in the - database, this is done by :meth:`~.export_edi`. - - :param list(browse_record) records: records to export as EDI - :return: UTF-8 encoded string containing the serialized records - """ - edi_list = [] - for record in records: - record_model_obj = self.pool.get(record._name) - edi_list += record_model_obj.edi_export(cr, uid, [record], context=context) - return self.serialize(edi_list) - - def get_document(self, cr, uid, edi_token, context=None): - """Retrieve the EDI document corresponding to the given edi_token. - - :return: EDI document string - :raise: ValueError if requested EDI token does not match any know document - """ - _logger.debug("get_document(%s)", edi_token) - edi_ids = self.search(cr, uid, [('name','=', edi_token)], context=context) - if not edi_ids: - raise ValueError('Invalid EDI token: %s' % edi_token) - edi = self.browse(cr, uid, edi_ids[0], context=context) - return edi.document - - def load_edi(self, cr, uid, edi_documents, context=None): - """Import the given EDI document structures into the system, using - :meth:`~.import_edi`. - - :param edi_documents: list of Python dicts containing the deserialized - version of EDI documents - :return: list of (model, id) pairs containing the model and database ID - of all records that were imported in the system - """ - ir_module = self.pool.get('ir.module.module') - res = [] - for edi_document in edi_documents: - module = edi_document.get('__module') - if module != 'base' and not ir_module.search(cr, uid, [('name','=',module),('state','=','installed')]): - raise osv.except_osv(_('Missing Application'), - _("The document you are trying to import requires the OpenERP `%s` application. " - "The OpenERP configuration assistant will help with this if you are connected as an administrator.")%(module,)) - model = edi_document.get('__model') - assert model, '__model attribute is required in each EDI document' - model_obj = self.pool.get(model) - record_id = model_obj.edi_import(cr, uid, edi_document, context=context) - res.append((model, record_id)) - return res - - def deserialize(self, edi_documents_string): - """Return deserialized version of the given EDI Document string. - - :param str|unicode edi_documents_string: UTF-8 string (or unicode) containing - JSON-serialized EDI document(s) - :return: Python object representing the EDI document(s) (usually a list of dicts) - """ - return json.loads(edi_documents_string) - - def export_edi(self, cr, uid, records, context=None): - """Export the given database records as EDI documents, stores them - permanently with a new unique EDI token, for later retrieval via :meth:`~.get_document`, - and returns the list of the new corresponding ``ir.edi.document`` records. - - :param records: list of browse_record of any model - :return: list of IDs of the new ``ir.edi.document`` entries, in the same - order as the provided ``records``. - """ - exported_ids = [] - for record in records: - document = self.generate_edi(cr, uid, [record], context) - token = self.new_edi_token(cr, uid, record) - self.create(cr, uid, { - 'name': token, - 'document': document - }, context=context) - exported_ids.append(token) - return exported_ids - - def import_edi(self, cr, uid, edi_document=None, edi_url=None, context=None): - """Import a JSON serialized EDI Document string into the system, first retrieving it - from the given ``edi_url`` if provided. - - :param str|unicode edi_document: UTF-8 string or unicode containing JSON-serialized - EDI Document to import. Must not be provided if - ``edi_url`` is given. - :param str|unicode edi_url: URL where the EDI document (same format as ``edi_document``) - may be retrieved, without authentication. - """ - if edi_url: - assert not edi_document, 'edi_document must not be provided if edi_url is given' - edi_document = urllib2.urlopen(edi_url).read() - assert edi_document, 'EDI Document is empty!' - edi_documents = self.deserialize(edi_document) - return self.load_edi(cr, uid, edi_documents, context=context) - - -class edi(object): - """Mixin class for Model objects that want be exposed as EDI documents. - Classes that inherit from this mixin class should override the - ``edi_import()`` and ``edi_export()`` methods to implement their - specific behavior, based on the primitives provided by this mixin.""" - - # private method, not RPC-exposed as it creates ir.model.data entries as - # SUPERUSER based on its parameters - def _edi_external_id(self, cr, uid, record, existing_id=None, existing_module=None, - context=None): - """Generate/Retrieve unique external ID for ``record``. - Each EDI record and each relationship attribute in it is identified by a - unique external ID, which includes the database's UUID, as a way to - refer to any record within any OpenERP instance, without conflict. - - For OpenERP records that have an existing "External ID" (i.e. an entry in - ir.model.data), the EDI unique identifier for this record will be made of - "%s:%s:%s" % (module, database UUID, ir.model.data ID). The database's - UUID MUST NOT contain a colon characters (this is guaranteed by the - UUID algorithm). - - For records that have no existing ir.model.data entry, a new one will be - created during the EDI export. It is recommended that the generated external ID - contains a readable reference to the record model, plus a unique value that - hides the database ID. If ``existing_id`` is provided (because it came from - an import), it will be used instead of generating a new one. - If ``existing_module`` is provided (because it came from - an import), it will be used instead of using local values. - - :param browse_record record: any browse_record needing an EDI external ID - :param string existing_id: optional existing external ID value, usually coming - from a just-imported EDI record, to be used instead - of generating a new one - :param string existing_module: optional existing module name, usually in the - format ``module:db_uuid`` and coming from a - just-imported EDI record, to be used instead - of local values - :return: the full unique External ID to use for record - """ - ir_model_data = self.pool.get('ir.model.data') - db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - ext_id = self.get_external_id(cr, uid, [record.id])[record.id] - if not ext_id: - ext_id = existing_id or safe_unique_id(db_uuid, record._name, record.id) - # ID is unique cross-db thanks to db_uuid (already included in existing_module) - module = existing_module or "%s:%s" % (record._module, db_uuid) - _logger.debug("%s: Generating new external ID `%s.%s` for %r", self._name, - module, ext_id, record) - ir_model_data.create(cr, openerp.SUPERUSER_ID, - {'name': ext_id, - 'model': record._name, - 'module': module, - 'res_id': record.id}) - else: - module, ext_id = ext_id.split('.') - if not ':' in module: - # this record was not previously EDI-imported - assert module == record._module, 'Module mismatch between record and its current'\ - 'external ID' - # ID is unique cross-db thanks to db_uuid - module = "%s:%s" % (module, db_uuid) - - return '%s.%s' % (module, ext_id) - - def edi_metadata(self, cr, uid, records, context=None): - """Return a list containing the boilerplate EDI structures for - exporting ``records`` as EDI, including - the metadata fields - - The metadata fields always include:: - - { - '__model': 'some.model', # record model - '__module': 'module', # require module - '__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! - '__version' : [6,1,0], # server version, to check compatibility. - '__attachments_': - } - - :param list(browse_record) records: records to export - :return: list of dicts containing boilerplate EDI metadata for each record, - at the corresponding index from ``records``. - """ - data_ids = [] - ir_attachment = self.pool.get('ir.attachment') - results = [] - version = version_tuple() - 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) - edi_dict = { - '__id': ext_id, - '__last_update': last_update_for(record), - '__model' : record._name, - '__module' : record._module, - '__version': version, - '__attachments': attachments, - } - results.append(edi_dict) - return results - - def edi_m2o(self, cr, uid, record, context=None): - """Return a m2o EDI representation for the given record. - - The EDI format for a many2one is:: - - ['unique_external_id', 'Document Name'] - """ - edi_ext_id = self._edi_external_id(cr, uid, record, context=context) - relation_model = record._model - name = relation_model.name_get(cr, uid, [record.id], context=context) - name = name and name[0][1] or False - return [edi_ext_id, name] - - def edi_o2m(self, cr, uid, records, edi_struct=None, context=None): - """Return a list representing a O2M EDI relationship containing - all the given records, according to the given ``edi_struct``. - This is basically the same as exporting all the record using - :meth:`~.edi_export` with the given ``edi_struct``, and wrapping - the results in a list. - - Example:: - - [ # O2M fields would be a list of dicts, with their - { '__id': 'module:db-uuid.id', # own __id. - '__last_update': 'iso date', # update date - 'name': 'some name', - #... - }, - # ... - ], - """ - result = [] - for record in records: - result += record._model.edi_export(cr, uid, [record], edi_struct=edi_struct, context=context) - return result - - def edi_m2m(self, cr, uid, records, context=None): - """Return a list representing a M2M EDI relationship directed towards - all the given records. - This is basically the same as exporting all the record using - :meth:`~.edi_m2o` and wrapping the results in a list. - - Example:: - - # M2M fields are exported as a list of pairs, like a list of M2O values - [ - ['module:db-uuid.id1', 'Task 01: bla bla'], - ['module:db-uuid.id2', 'Task 02: bla bla'] - ] - """ - return [self.edi_m2o(cr, uid, r, context=context) for r in records] - - def edi_export(self, cr, uid, records, edi_struct=None, context=None): - """Returns a list of dicts representing an edi.document containing the - records, and matching the given ``edi_struct``, if provided. - - :param edi_struct: if provided, edi_struct should be a dictionary - with a skeleton of the fields to export. - Basic fields can have any key as value, but o2m - values should have a sample skeleton dict as value, - to act like a recursive export. - For example, for a res.partner record:: - - edi_struct: { - 'name': True, - 'company_id': True, - 'address': { - 'name': True, - 'street': True, - } - } - - Any field not specified in the edi_struct will not - be included in the exported data. Fields with no - value (False) will be omitted in the EDI struct. - If edi_struct is omitted, no fields will be exported - """ - if edi_struct is None: - edi_struct = {} - fields_to_export = edi_struct.keys() - results = [] - for record in records: - edi_dict = self.edi_metadata(cr, uid, [record], context=context)[0] - for field in fields_to_export: - column = self._all_columns[field].column - value = getattr(record, field) - if not value: - continue - #if _fields[field].has_key('function') or _fields[field].has_key('related_columns'): - # # Do not Export Function Fields and related fields - # continue - elif column._type == 'many2one': - value = self.edi_m2o(cr, uid, value, context=context) - elif column._type == 'many2many': - value = self.edi_m2m(cr, uid, value, context=context) - elif column._type == 'one2many': - value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field, {}), context=context) - edi_dict[field] = value - results.append(edi_dict) - return results - - def _edi_get_object_by_name(self, cr, uid, name, model_name, context=None): - model = self.pool.get(model_name) - search_results = model.name_search(cr, uid, name, operator='=', context=context) - if len(search_results) == 1: - return model.browse(cr, uid, search_results[0][0], context=context) - return False - - def _edi_import_attachments(self, cr, uid, record_id, edi_document, context=None): - ir_attachment = self.pool.get('ir.attachment') - for attachment in edi_document.get('__attachments', []): - # check attachment data is non-empty and valid - file_data = None - try: - file_data = base64.b64decode(attachment.get('content')) - except TypeError: - pass - assert file_data, 'Incorrect/Missing attachment file content' - assert attachment.get('name'), 'Incorrect/Missing attachment name' - assert attachment.get('file_name'), 'Incorrect/Missing attachment file name' - assert attachment.get('file_name'), 'Incorrect/Missing attachment file name' - ir_attachment.create(cr, uid, {'name': attachment['name'], - 'datas_fname': attachment['file_name'], - 'res_model': self._name, - 'res_id': record_id, - # should be pure 7bit ASCII - 'datas': str(attachment['content']), - }, context=context) - - - def _edi_get_object_by_external_id(self, cr, uid, external_id, model, context=None): - """Returns browse_record representing object identified by the model and external_id, - or None if no record was found with this external id. - - :param external_id: fully qualified external id, in the EDI form - ``module:db_uuid:identifier``. - :param model: model name the record belongs to. - """ - ir_model_data = self.pool.get('ir.model.data') - # external_id is expected to have the form: ``module:db_uuid:model.random_name`` - ext_id_members = split_external_id(external_id) - db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - module = ext_id_members['module'] - ext_id = ext_id_members['id'] - ext_module = '%s:%s' % (module, ext_id_members['db_uuid']) - data_ids = ir_model_data.search(cr, uid, [('model','=',model), - ('name','=',ext_id), - ('module','in',[ext_module,module])]) - if data_ids: - model = self.pool.get(model) - data = ir_model_data.browse(cr, uid, data_ids[0], context=context) - result = model.browse(cr, uid, data.res_id, context=context) - return result - - def edi_import_relation(self, cr, uid, model, value, external_id, context=None): - """Imports a M2O/M2M relation EDI specification ``[external_id,value]`` for the - given model, returning the corresponding database ID: - - * First, checks if the ``external_id`` is already known, in which case the corresponding - database ID is directly returned, without doing anything else; - * If the ``external_id`` is unknown, attempts to locate an existing record - with the same ``value`` via name_search(). If found, the given external_id will - be assigned to this local record (in addition to any existing one) - * If previous steps gave no result, create a new record with the given - value in the target model, assign it the given external_id, and return - the new database ID - """ - _logger.debug("%s: Importing EDI relationship [%r,%r]", self._name, external_id, value) - target = self._edi_get_object_by_external_id(cr, uid, external_id, model, context=context) - need_new_ext_id = False - if not target: - _logger.debug("%s: Importing EDI relationship [%r,%r] - ID not found, trying name_get", - self._name, external_id, value) - target = self._edi_get_object_by_name(cr, uid, value, model, context=context) - need_new_ext_id = True - if not target: - _logger.debug("%s: Importing EDI relationship [%r,%r] - name not found, creating it!", - self._name, external_id, value) - # also need_new_ext_id here, but already been set above - res_id, name = self.name_create(cr, uid, value, context=context) - target = model.browse(cr, uid, res_id, context=context) - if need_new_ext_id: - ext_id_members = split_external_id(external_id) - # module name is never used bare when creating ir.model.data entries, in order - # to avoid being taken as part of the module's data, and cleanup up at next update - module = "%s:%s" % (ext_id_members['module'], ext_id_members['db_uuid']) - # create a new ir.model.data entry for this value - self._edi_external_id(cr, uid, target, existing_id=ext_id_members['id'], existing_module=module, context=context) - return target.id - - def edi_import(self, cr, uid, edi_document, context=None): - """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 - unique identifier, so that we can avoid duplication of records when importing. - - #. 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['__model'], "EDI Document Model and current model do not match: "\ - "'%s' (EDI) vs '%s' (current)" % (edi_document['__model'], - self._name) - - # 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']) - existing_id = self._edi_get_object_by_external_id(cr, uid, ext_id_members['full'], self._name, context=context) - if existing_id: - _logger.info("'%s' EDI Document with ID '%s' is already known, skipping import!", self._name, ext_id_members['full']) - return - - record_values = {} - o2m_todo = {} # o2m values are processed after their parent already exists - for field_name, field_value in edi_document.iteritems(): - # skip metadata and empty fields - if field_name.startswith('__') or field_value is None or field_value is False: - continue - field = self._all_columns[field_name].column - # skip function/related fields - if isinstance(field, fields.function): - _logger.warning("Unexpected function field value found in '%s' EDI document: '%s'" % (self._name, field_name)) - continue - relation_model = field._obj - if field._type == 'many2one': - record_values[field_name] = self.edi_import_relation(cr, uid, relation_model, - field_value[1], field_value[0], - context=context) - elif field._type == 'many2many': - record_values[field_name] = [self.edi_import_relation(cr, uid, relation_model, m2m_value[1], - m2m_value[0], context=context) - for m2m_value in field_value] - elif field._type == 'one2many': - # must wait until parent report is imported, as the parent relationship - # is often required in o2m child records - o2m_todo[field_name] = field_value - else: - record_values[field_name] = field_value - - module_ref = "%s:%s" % (ext_id_members['module'], ext_id_members['db_uuid']) - record_id = self.pool.get('ir.model.data')._update(cr, uid, self._name, module_ref, record_values, - xml_id=ext_id_members['id'], context=context) - - record_display, = self.name_get(cr, uid, [record_id], context=context) - - # process o2m values, connecting them to their parent on-the-fly - for o2m_field, o2m_value in o2m_todo.iteritems(): - field = self._all_columns[o2m_field].column - dest_model = self.pool.get(field._obj) - for o2m_line in o2m_value: - # link to parent record: expects an (ext_id, name) pair - o2m_line[field._fields_id] = (ext_id_members['full'], record_display[1]) - dest_model.edi_import(cr, uid, o2m_line, context=context) - - # process the attachments, if any - self._edi_import_attachments(cr, uid, record_id, edi_document, context=context) - - return record_id - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/openerp/addons/base/res/__init__.py b/openerp/addons/base/res/__init__.py index cb4c68b2a72..ba3e906497e 100644 --- a/openerp/addons/base/res/__init__.py +++ b/openerp/addons/base/res/__init__.py @@ -35,8 +35,6 @@ import res_lang import res_log import res_widget import ir_property -import edi_partner -import edi_res_company import wizard import report diff --git a/openerp/addons/base/res/edi_partner.py b/openerp/addons/base/res/edi_partner.py deleted file mode 100644 index b8b64fad9e6..00000000000 --- a/openerp/addons/base/res/edi_partner.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from osv import fields,osv -from base.ir import ir_edi - -RES_PARTNER_ADDRESS_EDI_STRUCT = { - 'name': True, - 'email': True, - 'street': True, - 'street2': True, - 'zip': True, - 'city': True, - 'country_id': True, - 'state_id': True, - 'phone': True, - 'fax': True, - 'mobile': True, -} - -RES_PARTNER_EDI_STRUCT = { - 'name': True, - 'ref': True, - 'lang': True, - 'website': True, - 'address': RES_PARTNER_ADDRESS_EDI_STRUCT -} - - -class res_partner(osv.osv, ir_edi.edi): - _inherit = "res.partner" - - def edi_export(self, cr, uid, records, edi_struct=None, context=None): - return super(res_partner,self).edi_export(cr, uid, records, - dict(RES_PARTNER_EDI_STRUCT), - context=context) - -class res_partner_address(osv.osv, ir_edi.edi): - _inherit = "res.partner.address" - - def edi_export(self, cr, uid, records, edi_struct=None, context=None): - return super(res_partner_address,self).edi_export(cr, uid, records, - dict(RES_PARTNER_ADDRESS_EDI_STRUCT), - context=context) - -class res_partner_bank(osv.osv, ir_edi.edi): - _inherit = "res.partner.bank" - diff --git a/openerp/addons/base/res/edi_res_company.py b/openerp/addons/base/res/edi_res_company.py deleted file mode 100644 index 3cf236756a4..00000000000 --- a/openerp/addons/base/res/edi_res_company.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2011 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -#from osv import osv -#from osv import fields -#from tools.translate import _ - -# TODO: CHECK below, seems useless -# -#class res_company(osv.osv): -# """Helper subclass for res.company providing util methods for working with -# companies in the context of EDI import/export. The res.company object -# itself is not EDI-exportable""" -# _inherit = "res.company" -# -# def edi_import_as_partner(self, cr, uid, edi_document, values=None, context=None): -# """ -# import company as a new partner -# company_address data used to add address to new partner -# -# edi_document is a dict to have company datas -# edi_document = { -# 'company_address': { -# 'street': True, -# 'street2': True, -# 'zip': True, -# 'city': True, -# 'state_id': True, -# 'country_id': True, -# 'email': True, -# 'phone': True, -# -# }, -# 'company_id': True, -# } -# values is a dict to have other datas of partner which are need to import of partner record -# values = { -# 'customer': True, -# 'supplier': True, -# } -# """ -# if values is None: -# values = {} -# partner_model = 'res.partner' -# partner_pool = self.pool.get(partner_model) -# xml_id = edi_document['company_id'][0] -# company_address = edi_document.get('company_address', False) -# partner_name = edi_document['company_id'][1] -# partner = partner_pool.edi_get_object(cr, uid, xml_id, partner_model, context=context) -# if not partner: -# partner = partner_pool.edi_get_object_by_name(cr, uid, partner_name, partner_model, context=context) -# -# if partner: - #FIXME -# record_xml = partner_pool._get_external_id(cr, uid, [partner.id], context=context) -# if record_xml: -# module, xml_id = record_xml -# xml_id = '%s.%s' % (module, xml_id) -# -# edi_document_partner = { -# '__model': partner_model, -# '__id' : xml_id, -# 'name' : partner_name, -# } -# if company_address: -# edi_document_partner['address'] = [company_address] -# -# edi_document_partner.update(values) -# return partner_pool.edi_import(cr, uid, edi_document_partner, context=context) -# -# def edi_export_address(self, cr, uid, records, edi_address_struct=None, context=None): -# """Return a dict representation of the address of each company record, suitable for -# inclusion in an EDI document, and matching the given edi_address_struct if provided. -# -# :param list(browse_record) records: list of companies to export -# :rtype: list(dict) -# :return: list of dicts, where each dict contains the address representation for -# the company record as the same index in ``records``. -# """ -# if context is None: -# context = {} -# res_partner = self.pool.get('res.partner') -# res_partner_address = self.pool.get('res.partner.address') -# if edi_address_struct is None: -# edi_address_struct = { -# 'street': True, -# 'street2': True, -# 'zip': True, -# 'city': True, -# 'state_id': True, -# 'country_id': True, -# 'email': True, -# 'phone': True, -# } -# results = [] -# for company in records: -# res = res_partner.address_get(cr, uid, [company.partner_id.id], ['default', 'contact', 'invoice']) -# addr_id = res['invoice'] or res['contact'] or res['default'] -# result = {} -# if addr_id: -# address = res_partner_address.browse(cr, uid, addr_id, context=context) -# result = res_partner_address.edi_export(cr, uid, [address], edi_struct=edi_address_struct, context=ctx)[0] -# results.append(result) -# return results - diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index 56e6c1b2408..659938a73a3 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -127,5 +127,3 @@ "access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0 "access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1 "access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0 -"access_ir_edi_all_read","access_ir_edi_all_read","model_ir_edi_document",,1,0,0,0 -"access_ir_edi_employee_create","access_ir_edi_employee_create","model_ir_edi_document","group_user",1,0,1,0 diff --git a/openerp/addons/base/test/test_edi_documents.yml b/openerp/addons/base/test/test_edi_documents.yml deleted file mode 100644 index c9695af73e1..00000000000 --- a/openerp/addons/base/test/test_edi_documents.yml +++ /dev/null @@ -1,43 +0,0 @@ -- - In order to test the basic EDI system, I will export a partner, - modify the exported EDI document to add an attachment and change - the data, and then re-import it and re-export it. - - with an attached file, check the result, the alter the data - and reimport it. -- - !python {model: ir.edi.document}: | - import json - partner_obj = self.pool.get('res.partner') - tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_agrolait'))]) - doc = self.get_document(cr, uid, tokens[0], context=context) - edi_doc, = json.loads(doc) - - # check content of the document - assert edi_doc.get('__id').endswith('.res_partner_agrolait'), 'Incorrect external ID' - assert edi_doc.get('__model') == 'res.partner', 'Incorrect/Missing __model' - assert edi_doc.get('__module') == 'base', 'Incorrect/Missing __module' - assert edi_doc.get('__last_update'), 'Missing __last_update' - - # try to import the document after changing the name and id, and attaching a - # file, and check that a new partner is returned - edi_doc['__id'] = 'base:xxd37f8a-xx55-11e0-xxdd-xx81124c8b50.res_partner_xxx' - edi_doc['name'] = 'AgroMilk' - attachment = { - 'name': 'Test file', - 'file_name': 'test.png', - 'content': 'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAKCAYAAACE2W/HAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oIDQ84BkjLWAYAAACNSURBVCjPjZKxDUJBDEOf/wbUbEHDhkxAzRjUDECBhMQYjIApuPBNpK+PpUinOPYll5Nt1iCJXifbSPpJZtHgXLUdu0FWpMEt87a/xtsmqjjNetPFyhuWYFuj7RcgQBNwXNGdw2CqY85yXWi5z7YBnmRyEHvg0YSXrLE9P30nwugOHHr+s5va4x+fofAGm1+JjnJICm0AAAAASUVORK5CYII=', - } - edi_doc['__attachments'] = [attachment] - doc = json.dumps([edi_doc]) - result, = self.import_edi(cr, uid, edi_document=doc) - assert result[0] == 'res.partner' and result[1] > ref('base.res_partner_agrolait'),\ - "Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_agrolait'), result) - - # export the same partner we just created, and see if the output matches the input - tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])]) - doc_output = self.get_document(cr, uid, tokens[0], context=context) - edi_doc_output, = json.loads(doc_output) - for attribute in ('__model', '__module', '__id', 'name', '__attachments'): - assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \ - 'Incorrect value for %s, expected %r, got %r' % (attribute, edi_doc.get(attribute), edi_doc_output.get(attribute)) \ No newline at end of file diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 09112f884fd..16bf50683c6 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -40,67 +40,6 @@ import openerp.tools as tools import openerp.modules import openerp.exceptions -class edi(netsvc.ExportService): - def exp_get_edi_document(self, edi_token, db_name): - res = None - if db_name: - cr = pooler.get_db(db_name).cursor() - else: - raise Exception("No database cursor found!") - pool = pooler.get_pool(db_name) - edi_pool = pool.get('ir.edi.document') - try: - res = edi_pool.get_document(cr, 1, edi_token) - finally: - cr.close() - return res - - def exp_import_edi_document(self, db, uid, passwd, edi_document, context=None): - res = None - cr = pooler.get_db(db).cursor() - pool = pooler.get_pool(db) - edi_pool = pool.get('ir.edi.document') - try: - res = edi_pool.import_edi(cr, uid, edi_document=edi_document, context=context) - cr.commit() - except: - print traceback.format_exc() - cr.rollback() - finally: - cr.close() - return res - - def exp_import_edi_url(self, db, uid, passwd, edi_url, context=None): - res = None - cr = pooler.get_db(db).cursor() - pool = pooler.get_pool(db) - edi_pool = pool.get('ir.edi.document') - try: - res = edi_pool.import_edi(cr, uid, edi_url=edi_url, context=context) - cr.commit() - except: - print traceback.format_exc() - cr.rollback() - finally: - cr.close() - return res - - def __init__(self, name="edi"): - netsvc.ExportService.__init__(self, name) - - def dispatch(self, method, auth, params): - if method in ['import_edi_document', 'import_edi_url']: - (db, uid, passwd ) = params[0:3] - security.check(db, uid, passwd) - elif method in ['get_edi_document']: - # params = params - # No security check for these methods - pass - else: - raise KeyError("Method not found: %s" % method) - fn = getattr(self, 'exp_'+method) - return fn(*params) - #.apidoc title: Exported Service methods #.apidoc module-mods: member-order: bysource @@ -836,8 +775,6 @@ def start_web_services(): objects_proxy() wizard() report_spool() - edi() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 982b2a08941714fbc3bfb3af20746619e5fc650a Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 00:54:39 +0200 Subject: [PATCH 50/56] [FIX] orm: typo in computation of Model._original_module bzr revid: odo@openerp.com-20111004225439-xnb77gtbl77onysm --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 9885c1a9697..80c0708b77f 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -818,7 +818,7 @@ class BaseModel(object): for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): parent_model = pool.get(parent_name) if not getattr(cls, '_original_module', None) and name == parent_model._name: - cls._original_module = parent_model._module + cls._original_module = parent_model._original_module if not parent_model: raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n' 'You may need to add a dependency on the parent class\' module.' % (name, parent_name)) From 5cc7c4002e28cf298a94693f76f11b5374b78af4 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 02:24:16 +0200 Subject: [PATCH 51/56] [IMP] res.currency: name_search() implementation to match name_get() bzr revid: odo@openerp.com-20111005002416-vz0vzfpb2uosxu4c --- openerp/addons/base/res/res_currency.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index 01d8b745e6f..248b379cafa 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -18,6 +18,7 @@ # along with this program. If not, see . # ############################################################################## +import re import time import netsvc from osv import fields, osv @@ -26,6 +27,8 @@ import tools from tools.misc import currency from tools.translate import _ +CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?') + class res_currency(osv.osv): def _current_rate(self, cr, uid, ids, name, arg, context=None): if context is None: @@ -101,6 +104,18 @@ class res_currency(osv.osv): r['date'] = currency_date return res + def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100): + if not args: + args = [] + if name: + ids = self.search(cr, user, ([('name','=',name)] + args), limit=limit, context=context) + name_match = CURRENCY_DISPLAY_PATTERN.match(name) + if not ids and name_match: + ids = self.search(cr, user, [('name','=', name_match.group(1))] + args, limit=limit, context=context) + else: + ids = self.search(cr, user, args, limit=limit, context=context) + return self.name_get(cr, user, ids, context=context) + def name_get(self, cr, uid, ids, context=None): if not ids: return [] From a25933be83c5f675648b651a782c9e0f04d66002 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 02:29:56 +0200 Subject: [PATCH 52/56] [IMP] res.currency: better/working default values bzr revid: odo@openerp.com-20111005002956-1tfe5u13qid3intk --- openerp/addons/base/res/res_currency.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index 248b379cafa..4e7029d5172 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -71,6 +71,8 @@ class res_currency(osv.osv): _defaults = { 'active': lambda *a: 1, 'position' : 'after', + 'rounding': 0.01, + 'accuracy': 4, } _sql_constraints = [ # this constraint does not cover all cases due to SQL NULL handling for company_id, From 33947042fda455e8b085e9da5512e527c5f6d595 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 03:01:36 +0200 Subject: [PATCH 53/56] [IMP] safe_eval: allow OPCODES for try/except/finally bzr revid: odo@openerp.com-20111005010136-xkowcqda3kres691 --- openerp/tools/safe_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/tools/safe_eval.py b/openerp/tools/safe_eval.py index be7b222f67f..02fcbc19c63 100644 --- a/openerp/tools/safe_eval.py +++ b/openerp/tools/safe_eval.py @@ -64,7 +64,7 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [ 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', # New in Python 2.7 - http://bugs.python.org/issue4715 : 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', - 'POP_JUMP_IF_TRUE' + 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY' ] if x in opmap)) _logger = logging.getLogger('safe_eval') From e510a9ce619e8b8ba95f1386e67f6717e3919239 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 10:46:21 +0200 Subject: [PATCH 54/56] [REM] base: removed unused pdf test file bzr revid: odo@openerp.com-20111005084621-totk30u0e0weol2o --- openerp/addons/base/test/partner.pdf | 118 --------------------------- 1 file changed, 118 deletions(-) delete mode 100644 openerp/addons/base/test/partner.pdf diff --git a/openerp/addons/base/test/partner.pdf b/openerp/addons/base/test/partner.pdf deleted file mode 100644 index 1b1c7f0daac..00000000000 --- a/openerp/addons/base/test/partner.pdf +++ /dev/null @@ -1,118 +0,0 @@ -%PDF-1.3 -%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com -% 'BasicFonts': class PDFDictionary -1 0 obj -% The standard fonts dictionary -<< /F1 2 0 R - /F2 3 0 R - /F3 4 0 R >> -endobj -% 'F1': class PDFType1Font -2 0 obj -% Font Helvetica -<< /BaseFont /Helvetica - /Encoding /WinAnsiEncoding - /Name /F1 - /Subtype /Type1 - /Type /Font >> -endobj -% 'F2': class PDFType1Font -3 0 obj -% Font Helvetica-Bold -<< /BaseFont /Helvetica-Bold - /Encoding /WinAnsiEncoding - /Name /F2 - /Subtype /Type1 - /Type /Font >> -endobj -% 'F3': class PDFType1Font -4 0 obj -% Font Times-Roman -<< /BaseFont /Times-Roman - /Encoding /WinAnsiEncoding - /Name /F3 - /Subtype /Type1 - /Type /Font >> -endobj -% 'Page1': class PDFPage -5 0 obj -% Page dictionary -<< /Contents 9 0 R - /MediaBox [ 0 - 0 - 841.8898 - 595.2756 ] - /Parent 8 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'R6': class PDFCatalog -6 0 obj -% Document Root -<< /Outlines 10 0 R - /PageMode /UseNone - /Pages 8 0 R - /Type /Catalog >> -endobj -% 'R7': class PDFInfo -7 0 obj -<< /Author (Generated by OpenERP, Fabien Pinckaers) - /CreationDate (20100421145542) - /Keywords () - /Producer (ReportLab http://www.reportlab.com) - /Subject (\(unspecified\)) - /Title (Partner) >> -endobj -% 'R8': class PDFPages -8 0 obj -% page tree -<< /Count 1 - /Kids [ 5 0 R ] - /Type /Pages >> -endobj -% 'R9': class PDFStream -9 0 obj -% page stream -<< /Filter [ /ASCII85Decode - /FlateDecode ] - /Length 524 >> -stream -Gat=(?#P3ie[>S,B2ME\)S7:@oo6@nF8`I+h##OY3a-n#Qm!>5jiD.9pQ@E1#Xj$=)27)F3RmD%fbqZh,T`gJd0/,?A3`0:_VVm`"l`>oOf9p&HGF7>WCGVJoD2&_+c#BDb)/&0>>rL5@]KY$9YOAqZHTJq8k]!o+?4a5\-KbB#NYJC'`~>endstream - -endobj -% 'R10': class PDFOutlines -10 0 obj -<< /Count 0 - /Type /Outlines >> -endobj -xref -0 11 -0000000000 65535 f -0000000113 00000 n -0000000233 00000 n -0000000398 00000 n -0000000573 00000 n -0000000740 00000 n -0000001017 00000 n -0000001152 00000 n -0000001395 00000 n -0000001500 00000 n -0000002169 00000 n -trailer -<< /ID - % ReportLab generated PDF document -- digest (http://www.reportlab.com) - [(\210\344\006bE\230CUUi\010[\)Qif) (\210\344\006bE\230CUUi\010[\)Qif)] - - /Info 7 0 R - /Root 6 0 R - /Size 11 >> -startxref -2221 -%%EOF From 57d1cbfe38305c5bfacd12efc40e41e77257e144 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 10:50:22 +0200 Subject: [PATCH 55/56] [IMP] base: update default web.base.url: port 8002->8069 bzr revid: odo@openerp.com-20111005085022-krq4qe95q84s7gol --- openerp/addons/base/base_data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index aa1d79cc9fe..94a3780653f 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -3,7 +3,7 @@ web.base.url - http://localhost:8002 + http://localhost:8069 ir.ui.menu.tree From e0a68f3be18e404b8eb38309fee117eb8d706fd6 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 5 Oct 2011 11:48:43 +0200 Subject: [PATCH 56/56] [REM] res.partner: moved opt_out field to email_template module bzr revid: odo@openerp.com-20111005094843-ov5p9em7mzxb2pkn --- openerp/addons/base/res/res_partner.py | 3 -- openerp/addons/base/res/res_partner_demo.xml | 30 -------------------- openerp/addons/base/res/res_partner_view.xml | 1 - 3 files changed, 34 deletions(-) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index f8d1e6dd980..54f61e6f41e 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -111,8 +111,6 @@ class res_partner(osv.osv): _order = "name" _columns = { 'name': fields.char('Name', size=128, required=True, select=True), - 'opt_out':fields.boolean('Opt-out', help="If checked, this partner will not receive any automated email \ -notifications, such as the availability of invoices."), 'date': fields.date('Date', select=1), 'title': fields.many2one('res.partner.title','Partner Firm'), 'parent_id': fields.many2one('res.partner','Parent Partner'), @@ -155,7 +153,6 @@ notifications, such as the availability of invoices."), 'address': [{'type': 'default'}], 'category_id': _default_category, 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c), - 'opt_out' : False, 'color': 0, } diff --git a/openerp/addons/base/res/res_partner_demo.xml b/openerp/addons/base/res/res_partner_demo.xml index f106702cd61..d3c4db106f9 100644 --- a/openerp/addons/base/res/res_partner_demo.xml +++ b/openerp/addons/base/res/res_partner_demo.xml @@ -94,7 +94,6 @@ 1 - www.asustek.com @@ -102,7 +101,6 @@ - www.agrolait.com @@ -111,7 +109,6 @@ 1 - www.camptocamp.com @@ -119,14 +116,12 @@ Syleam - Thymbra - www.thymbra.com/ @@ -134,7 +129,6 @@ 1 - www.axelor.com/ @@ -142,13 +136,11 @@ Tiny AT Work - www.tinyatwork.com/ Bank Wealthy and sons - www.wealthyandsons.com/ @@ -156,7 +148,6 @@ China Export - www.chinaexport.com/ @@ -166,14 +157,12 @@ 1 - www.distribpc.com/ Ecole de Commerce de Liege - www.eci-liege.info// @@ -184,7 +173,6 @@ 1 - Maxtor @@ -194,7 +182,6 @@ 1 - Seagate @@ -203,7 +190,6 @@ 1 - http://mediapole.net @@ -211,7 +197,6 @@ - www.balmerinc.com @@ -221,14 +206,12 @@ - Tecsas 3020170000003 - @@ -237,7 +220,6 @@ - Centrale d'achats BML @@ -246,7 +228,6 @@ - @@ -257,13 +238,11 @@ - Université de Liège - http://www.ulg.ac.be/ @@ -277,7 +256,6 @@ Dubois sprl - http://www.dubois.be/ @@ -285,20 +263,17 @@ Eric Dubois - Fabien Dupont - Lucie Vonck - @@ -306,7 +281,6 @@ notsotiny.be - @@ -314,7 +288,6 @@ - @@ -323,7 +296,6 @@ 1 0 - vicking-direct.be @@ -333,7 +305,6 @@ 1 - woodywoodpecker.com @@ -342,7 +313,6 @@ - http://www.zerooneinc.com/ diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 58a62285ab0..5f3ebf73f46 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -332,7 +332,6 @@ -