From 48dbf92a9c0751af0d27af0890e4d19275ab2027 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Fri, 27 May 2011 12:55:23 +0530 Subject: [PATCH 001/144] [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 002/144] [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 003/144] [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 004/144] [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 005/144] [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 006/144] [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 007/144] [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 008/144] [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 009/144] [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 010/144] [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 011/144] [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 012/144] [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 013/144] [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 014/144] [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 015/144] [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 b5050763692468b73263455c54b28ad0351bba5c Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Fri, 24 Jun 2011 14:16:56 +0530 Subject: [PATCH 016/144] [FIX] add get method in refrence field and check if refrence id not exists then return false. lp bug: https://launchpad.net/bugs/783961 fixed bzr revid: ysa@tinyerp.com-20110624084656-c2jd3xskk10fwt35 --- openerp/osv/fields.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index f55e4b9c027..aa042ca6541 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -148,9 +148,23 @@ class integer_big(_column): class reference(_column): _type = 'reference' + _classic_read = False # post-process to handle missing target + def __init__(self, string, selection, size, **args): _column.__init__(self, string=string, size=size, selection=selection, **args) + def get(self, cr, obj, ids, name, uid=None, context=None, values=None): + result = {} + # copy initial values fetched previously. + for row in values: + result[row['id']] = row[name] + # verify target object exists + for id, value in result.iteritems(): + if value: + model, res_id = value.split(',') + if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context): + result[id] = False + return result class char(_column): _type = 'char' From 5ac271fa1fa85fc24a34c76ca5258d45a422955f Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Mon, 27 Jun 2011 13:59:45 +0530 Subject: [PATCH 017/144] [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 018/144] [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 019/144] [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 020/144] [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 021/144] [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 022/144] [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 023/144] [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 024/144] [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 025/144] [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 026/144] [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 027/144] [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 028/144] [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 029/144] [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 75e0749542e51ce76fa29a9d2f4b6a02cb4067d1 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 1 Sep 2011 20:12:54 +0200 Subject: [PATCH 030/144] [IMP] ir.values: separated defaults and actions API, added different views This should prepare for a future split of the two parts currently composing ir.values entries: user defaults and action bindings to a model's UI. bzr revid: odo@openerp.com-20110901181254-g14lyeogs0wv23bz --- openerp/addons/base/ir/ir.xml | 130 +++-- openerp/addons/base/ir/ir_values.py | 511 ++++++++++++++------ openerp/addons/base/test/test_ir_values.yml | 3 + 3 files changed, 415 insertions(+), 229 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index 011ad437cca..eda044336df 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -7,44 +7,50 @@ ir.values.form.action ir.values form - 20 -
- + + - + + - + - + + + + + +
- - + + ir.values.form.defaults + ir.values + form + +
+ + + + + + + - - - - + + + - - - - - - - - - - - +
@@ -53,11 +59,11 @@ ir.values tree - + - + @@ -73,7 +79,7 @@ - + @@ -81,23 +87,21 @@ - Client Events + Action Bindings ir.actions.act_window ir.values form tree,form [('key','=','action')] - {'read':'default','default_object':1} + {'default_object':1,'default_key':'action'} - tree - form @@ -105,54 +109,27 @@ - - - - - - - ir.values.form - ir.values - form - -
- - - - - - - - - - - - -
- - - ir.values.tree - ir.values - tree - - - - - - - - - - - - - - Client Actions Connections + + User-defined Defaults ir.actions.act_window ir.values form - - {'read':'default'} + tree,form + + [('key','=','default')] + {'default_object':0,'default_key':'default','default_key2':''} + + + + tree + + + + + + form + + @@ -342,6 +319,7 @@ + diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index c2a59ada55e..c75831f9c2f 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -28,7 +28,76 @@ EXCLUDED_FIELDS = set(( 'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', 'report_sxw_content_data', 'report_rml_content_data', 'search_view', )) +#: Possible slots to bind an action to with :meth:`~.set_action` +ACTION_SLOTS = [ + "client_action_multi", # sidebar wizard action + "client_print_multi", # sidebar report printing button + "client_action_relate", # sidebar related link + "tree_but_open", # double-click on item in tree view + "tree_but_action", # deprecated: same as tree_but_open + ] + + class ir_values(osv.osv): + """Holds internal model-specific action bindings and user-defined default + field values. definitions. This is a legacy internal model, mixing + two different concepts, and will likely be updated or replaced in a + future version by cleaner, separate models. You should not depend + explicitly on it. + + The purpose of each ``ir.values`` entry depends on its type, defined + by the ``key`` column: + + * 'default': user-defined default values, used when creating new + records of this model: + * 'action': binding of an action to a particular *action slot* of + this model, making the action easily available in the user + interface for this model. + + The ``key2`` column acts as a qualifier, further refining the type + of the entry. The possible values are: + + * for 'default' entries: an optional condition restricting the + cases where this particular default value will be applicable, + or ``False`` for no condition + * for 'action' entries: the ``key2`` qualifier is one of the available + action slots, defining how this action can be invoked: + + * ``'client_print_multi'`` for report printing actions that will + be available on views displaying items from this model + * ``'client_action_multi'`` for assistants (wizards) actions + that will be available in views displaying objects of this model + * ``'client_action_relate'`` for links towards related documents + that should be available in views displaying objects of this model + * ``'tree_but_open'`` for actions that will be triggered when + double-clicking an item from this model in a hierarchical tree view + + Each entry is specific to a model (``model`` column), and for ``'actions'`` + type, may even be made specific to a given record of that model when the + ``res_id`` column contains a record ID (``False`` means it's global for + all records). + + The content of the entry is defined by the ``value`` column, which may either + contain an arbitrary value (for default values - when the ``object`` column + is False), or a reference string defining the action that should be executed. + + .. rubric:: Usage: default values + + The ``'default'`` entries are usually defined manually by the + users, and set by their UI clients calling :meth:`~.set_default`. + These default values are then automatically used by the + ORM every time a new record is about to be created, i.e. when + :meth:`~openerp.osv.osv.osv.default_get` + or :meth:`~openerp.osv.osv.osv.create` are called. + + .. rubric:: Usage: action bindings + + Business applications will usually bind their actions during + installation, and OpenERP UI clients will apply them as defined, + based on the list of actions included in the result of + :meth:`~openerp.osv.osv.osv.fields_view_get`, + or directly returned by explicit calls to :meth:`~.get_actions`. + """ _name = 'ir.values' def _value_unpickle(self, cursor, user, ids, name, arg, context=None): @@ -38,7 +107,7 @@ class ir_values(osv.osv): if not report.object and value: try: value = str(pickle.loads(value)) - except: + except Exception: pass res[report.id] = value return res @@ -53,44 +122,69 @@ class ir_values(osv.osv): value = pickle.dumps(value) self.write(cursor, user, id, {name[:-9]: value}, context=ctx) - def onchange_object_id(self, cr, uid, ids, object_id, context={}): + def onchange_object_id(self, cr, uid, ids, object_id, context=None): if not object_id: return {} act = self.pool.get('ir.model').browse(cr, uid, object_id, context=context) return { 'value': {'model': act.model} } - def onchange_action_id(self, cr, uid, ids, action_id, context={}): + def onchange_action_id(self, cr, uid, ids, action_id, context=None): if not action_id: return {} act = self.pool.get('ir.actions.actions').browse(cr, uid, action_id, context=context) return { 'value': {'value_unpickle': act.type+','+str(act.id)} } + def onchange_key(self, cr, uid, ids, key, context=None): + return { + 'value': {'object': key == 'action'} + } + _columns = { - 'name': fields.char('Name', size=128), - 'model_id': fields.many2one('ir.model', 'Object', size=128, - help="This field is not used, it only helps you to select a good model."), - 'model': fields.char('Object Name', size=128, select=True), - 'action_id': fields.many2one('ir.actions.actions', 'Action', - help="This field is not used, it only helps you to select the right action."), - 'value': fields.text('Value'), + 'name': fields.char('Name', size=128, required=True), + 'model': fields.char('Model Name', size=128, select=True, required=True, + help="Model to which this entry applies"), + + # TODO: model_id and action_id should be read-write function fields + 'model_id': fields.many2one('ir.model', 'Model (change only)', size=128, + help="Model to which this entry applies - " + "helper field for setting a model, will " + "automatically set the correct model name"), + 'action_id': fields.many2one('ir.actions.actions', 'Action (change only)', + help="Action bound to this entry - " + "helper field for binding an action, will " + "automatically set the correct reference"), + + 'value': fields.text('Value', help="Default value (pickled) or reference to an action"), 'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle, - method=True, type='text', string='Value'), - 'object': fields.boolean('Is Object'), - 'key': fields.selection([('action','Action'),('default','Default')], 'Type', size=128, select=True), - 'key2' : fields.char('Event Type',help="The kind of action or button in the client side that will trigger the action.", size=128, select=True), - 'meta': fields.text('Meta Datas'), - 'meta_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle, - method=True, type='text', string='Metadata'), - 'res_id': fields.integer('Object ID', help="Keep 0 if the action must appear on all resources.", select=True), - 'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True), - 'company_id': fields.many2one('res.company', 'Company', select=True) + type='text', + string='Default value or action reference'), + 'object': fields.boolean('No value serialization', help="Should be enabled when Type is Action"), + 'key': fields.selection([('action','Action'),('default','Default')], + 'Action Type', size=128, select=True, required=True, + help="- Action: an action attached to one slot of the given model\n" + "- Default: a default value for a model field"), + 'key2' : fields.char('Action Qualifier', size=128, select=True, + help="For actions, one of the possible action slots: \n" + " - client_action_multi\n" + " - client_print_multi\n" + " - client_action_relate\n" + " - tree_but_open\n" + "For defaults, an optional condition" + ,), + 'res_id': fields.integer('Record ID', select=True, + help="Database identifier of the record to which this applies." + "0 = for all records"), + 'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True, + help="If set, action binding only applies for this user."), + 'company_id': fields.many2one('res.company', 'Company', ondelete='cascade', select=True, + help="If set, action binding only applies for this company") } _defaults = { - 'key': lambda *a: 'action', - 'key2': lambda *a: 'tree_but_open', - 'company_id': lambda *a: False + 'key': 'action', + 'key2': 'tree_but_open', + 'object': True, } def _auto_init(self, cr, context=None): @@ -99,140 +193,251 @@ class ir_values(osv.osv): if not cr.fetchone(): cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)') - def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False): + def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False): + """Defines a default value for the given model and field_name. Any previous + default will be replaced and lost in the process. + + :param string model: model name + :param string field_name: field name to which the default applies + :param value: the default field value to set + :type value: any serializable Python value + :param bool for_all_users: whether the default should apply to everybody or only + the user calling the method + :param int company_id: optional ID of the company to which the default should + apply. If omitted, the default will be global. If True + is passed, the current user's company will be used. + :param string condition: optional condition specification that can be used to + restrict the applicability of the default values + (e.g. based on another field's value) + :return: id of the newly created ir.values entry + """ if isinstance(value, unicode): value = value.encode('utf8') - if not isobject: - value = pickle.dumps(value) - if meta: - meta = pickle.dumps(meta) - assert isinstance(models, (list, tuple)), models - ids_res = [] - for model in models: - if isinstance(model, (list, tuple)): - model,res_id = model - else: - res_id = False - if replace: - search_criteria = [ - ('key', '=', key), - ('key2', '=', key2), - ('model', '=', model), - ('res_id', '=', res_id), - ('user_id', '=', preserve_user and uid) - ] - if key in ('meta', 'default'): - search_criteria.append(('name', '=', name)) - else: - search_criteria.append(('value', '=', value)) + if company_id is True: + # should be company-specific, need to get company id + user = self.pool.get('res.users').browse(cr, uid, uid) + company_id = user.company_id.id - self.unlink(cr, uid, self.search(cr, uid, search_criteria)) - vals = { - 'name': name, - 'value': value, - 'model': model, - 'object': isobject, - 'key': key, - 'key2': key2 and key2[:200], - 'meta': meta, - 'user_id': preserve_user and uid, - } - if company: - cid = self.pool.get('res.users').browse(cr, uid, uid, context={}).company_id.id - vals['company_id']=cid - if res_id: - vals['res_id']= res_id - ids_res.append(self.create(cr, uid, vals)) - return ids_res + # remove existing defaults for the same scope + search_criteria = [ + ('key', '=', 'default'), + ('key2', '=', condition), + ('model', '=', model), + ('name', '=', field_name), + ('user_id', '=', False if for_all_users else uid), + ('company_id','=', company_id) + ] + self.unlink(cr, uid, self.search(cr, uid, search_criteria)) + + return self.create(cr, uid, { + 'name': field_name, + 'value': pickle.dumps(value), + 'model': model, + 'object': False, + 'key': 'default', + 'key2': condition and condition[:200], + 'user_id': False if for_all_users else uid, + 'company_id': company_id, + }) + + def get_defaults(self, cr, uid, model, condition=False): + """Returns any user-defined values registered for the given model. + This is not field-specific, but an optional ``condition`` can be + provided to query for default values that should be applied only + when the given condition is met (usually another field's value). + Defaults that have been set for the user herself will have higher + priority that those that have been set for everyone + (see :meth:`~.set_default`). + + :param string model: model name + :param string condition: optional condition specification that can be used to + restrict the applicability of the default values + (e.g. based on another field's value) + :return: list of default values tuples of the form ``(id, field_name, value)`` + (``id`` is the ID of the default entry, usually irrelevant) + """ + # use a direct SQL query for performance reasons, + # this is called very often + query = """SELECT v.id, v.name, v.value FROM ir_values v + LEFT JOIN res_users u ON (v.user_id = u.id) + WHERE v.key = %%s AND v.model = %%s + AND (v.user_id = %%s OR v.user_id IS NULL) + AND (u.company_id IS NULL OR + u.company_id = + (SELECT company_id from res_users where id = %%s) + ) + %s + ORDER BY v.user_id, u.company_id""" + query = query % ('AND v.key2 = %s' if condition else '') + params = ('default', model, uid, uid) + if condition: + params += (condition,) + cr.execute(query, params) + + # keep only the highest priority default for each field + defaults = {} + for row in cr.dictfetchall(): + defaults.setdefault(row['name'], + (row['id'], row['name'], pickle.loads(row['value'].encode('utf-8')))) + return defaults.values() + + def set_action(self, cr, uid, name, action_slot, model, action, res_id=False): + """Binds an the given action to the given model's action slot - for later + retrieval via :meth:`~.get_actions`. Any existing binding of the same action + to the same slot is first removed, allowing an update of the action's name. + See the class description for more details about the various action + slots: :class:`~ir_values`. + + :param string name: action label, usually displayed by UI client + :param string action_slot: the action slot to which the action should be + bound to - one of ``client_action_multi``, + ``client_print_multi``, ``client_action_relate``, + ``tree_but_open``. + :param string model: model name + :param string action: action reference, in the form ``'model,id'`` + :param int res_id: optional record id - will bind the action only to a + specific record of the model, not all records. + :return: id of the newly created ir.values entry + """ + assert isinstance(action, basestring) and ',' in action, \ + 'Action definition must be an action reference, e.g. "ir.actions.act_window,42"' + assert action_slot in ACTION_SLOTS, \ + 'Action slot (%s) must be one of: %r' % (action_slot, ACTION_SLOTS) + + # remove existing action definition of same slot and value + search_criteria = [ + ('key', '=', 'action'), + ('key2', '=', action_slot), + ('model', '=', model), + ('res_id', '=', res_id), + ('value', '=', action), + ] + self.unlink(cr, uid, self.search(cr, uid, search_criteria)) + + return self.create(cr, uid, { + 'key': 'action', + 'key2': action_slot, + 'object': True, + 'model': model, + 'res_id': res_id or False, + 'name': name, + 'value': action, + }) + + def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None): + """Retrieves the list of actions bound to the given model's action slot. + See the class description for more details about the various action + slots: :class:`~.ir_values`. + + :param string action_slot: the action slot to which the actions should be + bound to - one of ``client_action_multi``, + ``client_print_multi``, ``client_action_relate``, + ``tree_but_open``. + :param string model: model name + :param int res_id: optional record id - will bind the action only to a + specific record of the model, not all records. + :return: list of action tuples of the form ``(id, name, action_def)``, + where ``id`` is the ID of the default entry, ``name`` is the + action label, and ``action_def`` is a dict containing the + action definition as obtained by calling + :meth:`~openerp.osv.osv.osv.read` on the action record. + """ + assert action_slot in ACTION_SLOTS, 'Illegal action slot value: %s' % action_slot + # use a direct SQL query for performance reasons, + # this is called very often + query = """SELECT v.id, v.name, v.value FROM ir_values v + WHERE v.key = %s AND v.key2 = %s + AND v.model = %s + AND (v.res_id = %s + OR v.res_id IS NULL + OR v.res_id = 0) + ORDER BY v.id""" + cr.execute(query, ('action', action_slot, model, res_id or None)) + results = {} + for action in cr.dictfetchall(): + action_model,id = action['value'].split(',') + fields = [ + field + for field in self.pool.get(action_model)._all_columns + if field not in EXCLUDED_FIELDS] + # FIXME: needs cleanup + try: + action_def = self.pool.get(action_model).read(cr, uid, int(id), fields, context) + if action_def: + if action_model in ('ir.actions.report.xml','ir.actions.act_window', + 'ir.actions.wizard'): + groups = action_def.get('groups_id') + if groups: + cr.execute('SELECT 1 FROM res_groups_users_rel WHERE gid IN %s AND uid=%s', + (tuple(groups), uid)) + if not cr.fetchone(): + if action['name'] == 'Menuitem': + raise osv.except_osv('Error !', + 'You do not have the permission to perform this operation !!!') + continue + # keep only the first action registered for each action name + results[action['name']] = (action['id'], action['name'], action_def) + except except_orm, e: + continue + return results.values() + + def _map_legacy_model_list(self, model_list, map_fn): + """Apply map_fn to the various models passed, according to + legacy way to specify models/records. + + :param model_list: list of models/records reference in the form ``[model,..]`` + or ``[(model,res_id), ...]`` + :param map_fn: the map function to apply - should expect 2 arguments: + ``model`` and ``res_id``. + """ + assert isinstance(model_list, (list, tuple)), \ + "model_list should be in the form [model,..] or [(model,res_id), ..]" + results = [] + for model in model_list: + res_id = False + if isinstance(model, (list, tuple)): + model, res_id = model + result = map_fn(model, res_id) + # some of the functions return one result at a time (tuple or id) + # and some return a list of many of them - care for both + if isinstance(result,list): + results.extend(result) + else: + results.append(result) + return results + + # Backards-compatibility adapter layer to retrofit into split API + def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False): + """Deprecated legacy method to set default values and bind actions to models' action slots. + Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default` + (``key=='default'``) or :meth:`~.set_action` (``key == 'action'``). + + :deprecated: As of v6.1, ``set_default()`` or ``set_action()`` should be used directly. + """ + assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']" + if key == 'default': + def do_set(model,res_id): + return self.set_default(cr, uid, model, field_name=name, value=value, + for_all_users=(not preserve_user), company_id=company, + condition=key2) + elif key == 'action': + def do_set(model,res_id): + return self.set_action(cr, uid, name, action_slot=key2, model=model, action=value, res_id=res_id) + return self._map_legacy_model_list(models, do_set) def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True): - if context is None: - context = {} - result = [] - assert isinstance(models, (list, tuple)), models + """Deprecated legacy method to get the list of default values or actions bound to models' action slots. + Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults` + (``key=='default'``) or :meth:`~.get_actions` (``key == 'action'``) - for m in models: - if isinstance(m, (list, tuple)): - m, res_id = m - else: - res_id = False + :deprecated: As of v6.1, ``get_defaults()`` or ``get_actions()`` should be used directly. - where = ['key=%s','model=%s'] - params = [key, str(m)] - if key2: - where.append('key2=%s') - params.append(key2[:200]) - elif key2_req and not meta: - where.append('key2 is null') - if res_id_req and (models[-1][0] == m): - if res_id: - where.append('res_id=%s') - params.append(res_id) - else: - where.append('(res_id is NULL)') - elif res_id: - if (models[-1][0]==m): - where.append('(res_id=%s or (res_id is null))') - params.append(res_id) - else: - where.append('res_id=%s') - params.append(res_id) - order = 'id' - if key == 'default': - # Make sure we get first the values for specific users, then - # the global values. The map/filter below will retain the first - # value for any given name. The 'order by' will put the null - # values last; this may be postgres specific (it is the - # behavior in postgres at least since 8.2). - order = 'user_id' - where.append('(user_id=%s or (user_id IS NULL)) order by '+ order) - params.append(uid) - clause = ' and '.join(where) - cr.execute('select id,name,value,object,meta, key from ir_values where ' + clause, params) - result = cr.fetchall() - if result: - break - - if not result: - return [] - - def _result_get(x, keys): - if x[1] in keys: - return False - keys.append(x[1]) - if x[3]: - model,id = x[2].split(',') - # FIXME: It might be a good idea to opt-in that kind of stuff - # FIXME: instead of arbitrarily removing random fields - fields = [ - field - for field in self.pool.get(model).fields_get_keys(cr, uid) - if field not in EXCLUDED_FIELDS] - - try: - datas = self.pool.get(model).read(cr, uid, [int(id)], fields, context) - except except_orm, e: - return False - datas = datas and datas[0] - if not datas: - return False - else: - datas = pickle.loads(x[2].encode('utf-8')) - if meta: - return (x[0], x[1], datas, pickle.loads(x[4])) - return (x[0], x[1], datas) - keys = [] - res = filter(None, map(lambda x: _result_get(x, keys), result)) - res2 = res[:] - for r in res: - if isinstance(r[2], dict) and r[2].get('type') in ('ir.actions.report.xml','ir.actions.act_window','ir.actions.wizard'): - groups = r[2].get('groups_id') - if groups: - cr.execute('SELECT COUNT(1) FROM res_groups_users_rel WHERE gid IN %s AND uid=%s',(tuple(groups), uid)) - cnt = cr.fetchone()[0] - if not cnt: - res2.remove(r) - if r[1] == 'Menuitem' and not res2: - raise osv.except_osv('Error !','You do not have the permission to perform this operation !!!') - return res2 -ir_values() + """ + assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']" + if key == 'default': + def do_get(model,res_id): + return self.get_defaults(cr, uid, model, condition=key2) + elif key == 'action': + def do_get(model,res_id): + return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context) + return self._map_legacy_model_list(models, do_get) diff --git a/openerp/addons/base/test/test_ir_values.yml b/openerp/addons/base/test/test_ir_values.yml index b4c08db31ff..da26b9837b5 100644 --- a/openerp/addons/base/test/test_ir_values.yml +++ b/openerp/addons/base/test/test_ir_values.yml @@ -21,5 +21,8 @@ - !python {model: ir.values }: | d = self.get(cr, uid, 'default', False, ['unexisting_model']) + assert len(d) == 1, "Only one default must be returned per field" assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value." assert d[0][2] == 'specific value', "Can't retrieve the created default value." + +# TODO: ADD tests for action bindings too, the other half of ir_values. \ No newline at end of file From 38ddb76960c21af0bf097112abd1eca78316bea1 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 2 Sep 2011 12:15:30 +0200 Subject: [PATCH 031/144] [IMP] ir.values: minor cleanup/view improvements bzr revid: odo@openerp.com-20110902101530-13r328uwhwdpudn5 --- openerp/addons/base/ir/ir.xml | 3 +-- openerp/addons/base/ir/ir_values.py | 17 ++++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index eda044336df..3f9c48b467f 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -42,7 +42,7 @@ - + @@ -63,7 +63,6 @@ - diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index c75831f9c2f..f3faacfd4d4 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -162,10 +162,10 @@ class ir_values(osv.osv): string='Default value or action reference'), 'object': fields.boolean('No value serialization', help="Should be enabled when Type is Action"), 'key': fields.selection([('action','Action'),('default','Default')], - 'Action Type', size=128, select=True, required=True, + 'Type', size=128, select=True, required=True, help="- Action: an action attached to one slot of the given model\n" "- Default: a default value for a model field"), - 'key2' : fields.char('Action Qualifier', size=128, select=True, + 'key2' : fields.char('Qualifier', size=128, select=True, help="For actions, one of the possible action slots: \n" " - client_action_multi\n" " - client_print_multi\n" @@ -174,7 +174,7 @@ class ir_values(osv.osv): "For defaults, an optional condition" ,), 'res_id': fields.integer('Record ID', select=True, - help="Database identifier of the record to which this applies." + help="Database identifier of the record to which this applies. " "0 = for all records"), 'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True, help="If set, action binding only applies for this user."), @@ -381,14 +381,9 @@ class ir_values(osv.osv): continue return results.values() - def _map_legacy_model_list(self, model_list, map_fn): + def _map_legacy_model_list(self, model_list, map_fn, merge_results=False): """Apply map_fn to the various models passed, according to legacy way to specify models/records. - - :param model_list: list of models/records reference in the form ``[model,..]`` - or ``[(model,res_id), ...]`` - :param map_fn: the map function to apply - should expect 2 arguments: - ``model`` and ``res_id``. """ assert isinstance(model_list, (list, tuple)), \ "model_list should be in the form [model,..] or [(model,res_id), ..]" @@ -400,7 +395,7 @@ class ir_values(osv.osv): result = map_fn(model, res_id) # some of the functions return one result at a time (tuple or id) # and some return a list of many of them - care for both - if isinstance(result,list): + if merge_results: results.extend(result) else: results.append(result) @@ -440,4 +435,4 @@ class ir_values(osv.osv): elif key == 'action': def do_get(model,res_id): return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context) - return self._map_legacy_model_list(models, do_get) + return self._map_legacy_model_list(models, do_get, merge_results=True) From e0624cbc954b946bb962fc8da252eb1ffed80cc8 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 2 Sep 2011 14:40:38 +0200 Subject: [PATCH 032/144] [IMP] ir.values: smoke test for action bindings bzr revid: odo@openerp.com-20110902124038-36ip41ccytr2f9vt --- openerp/addons/base/test/test_ir_values.yml | 54 +++++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/test/test_ir_values.yml b/openerp/addons/base/test/test_ir_values.yml index da26b9837b5..a8912e0ad3b 100644 --- a/openerp/addons/base/test/test_ir_values.yml +++ b/openerp/addons/base/test/test_ir_values.yml @@ -2,27 +2,71 @@ Create some default value for some (non-existing) model, for all users. - !python {model: ir.values }: | - self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'global value') + self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'global value') - Retrieve it. - !python {model: ir.values }: | # d is a list of triple (id, name, value) d = self.get(cr, uid, 'default', False, ['unexisting_model']) - assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value." + assert d[0][1] == 'my_test_field', "Can't retrieve the created default value." assert d[0][2] == 'global value', "Can't retrieve the created default value." - Do it again but for a specific user. - !python {model: ir.values }: | - self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'specific value', preserve_user=True) + self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'specific value', preserve_user=True) - Retrieve it and check it is the one for the current user. - !python {model: ir.values }: | d = self.get(cr, uid, 'default', False, ['unexisting_model']) assert len(d) == 1, "Only one default must be returned per field" - assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value." + assert d[0][1] == 'my_test_field', "Can't retrieve the created default value." assert d[0][2] == 'specific value', "Can't retrieve the created default value." +- + Create some action bindings for a non-existing model +- + !python {model: ir.values }: | + self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True) + self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True) + self.set(cr, uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True) + self.set(cr, uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,2', isobject=True) + self.set(cr, uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True) +- + Replace one action binding to set a new name +- + !python {model: ir.values }: | + self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True) +- + Retrieve the action bindings and check they're correct +- + !python {model: ir.values }: | + actions = self.get(cr, uid, 'action', 'tree_but_open', ['unexisting_model']) + assert len(actions) == 2, "Mismatching number of bound actions" + #first action + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'OnDblClick Action 2', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 11, 'Bound action does not match definition' + #second action - this ones comes last because it was re-created with a different name + assert len(actions[1]) == 3, "Malformed action definition" + assert actions[1][1] == 'OnDblClick Action New', 'Re-Registering an action should replace it' + assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == 10, 'Bound action does not match definition' -# TODO: ADD tests for action bindings too, the other half of ir_values. \ No newline at end of file + actions = self.get(cr, uid, 'action', 'client_action_multi', ['unexisting_model']) + assert len(actions) == 1, "Mismatching number of bound actions" + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'Side Wizard', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 12, 'Bound action does not match definition' + + actions = self.get(cr, uid, 'action', 'client_print_multi', ['unexisting_model']) + assert len(actions) == 1, "Mismatching number of bound actions" + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'Nice Report', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 2, 'Bound action does not match definition' + + actions = self.get(cr, uid, 'action', 'client_action_relate', ['unexisting_model']) + assert len(actions) == 1, "Mismatching number of bound actions" + assert len(actions[0]) == 3, "Malformed action definition" + assert actions[0][1] == 'Related Stuff', 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 14, 'Bound action does not match definition' From 32020e13531267301abb0e58711bed70c65ff8e7 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 2 Sep 2011 14:56:48 +0200 Subject: [PATCH 033/144] [FIX] ir.values: re-binding of actions should properly delete the previous one bzr revid: odo@openerp.com-20110902125648-x0cir5m7818h9ba6 --- openerp/addons/base/ir/ir_values.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index f3faacfd4d4..acb0da72159 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -309,7 +309,7 @@ class ir_values(osv.osv): ('key', '=', 'action'), ('key2', '=', action_slot), ('model', '=', model), - ('res_id', '=', res_id), + ('res_id', '=', res_id or 0), # int field -> NULL == 0 ('value', '=', action), ] self.unlink(cr, uid, self.search(cr, uid, search_criteria)) @@ -319,7 +319,7 @@ class ir_values(osv.osv): 'key2': action_slot, 'object': True, 'model': model, - 'res_id': res_id or False, + 'res_id': res_id, 'name': name, 'value': action, }) From d0af2f830c933a5dab635170d45b79a8278b99aa Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 2 Sep 2011 17:14:42 +0200 Subject: [PATCH 034/144] [FIX] ir.values: get_defaults() should cut the condition at 200 chars to match the conditions that were set bzr revid: odo@openerp.com-20110902151442-rqrh62z67g8k921o --- openerp/addons/base/ir/ir_values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index acb0da72159..ca2973f948e 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -271,7 +271,7 @@ class ir_values(osv.osv): query = query % ('AND v.key2 = %s' if condition else '') params = ('default', model, uid, uid) if condition: - params += (condition,) + params += (condition[:200],) cr.execute(query, params) # keep only the highest priority default for each field From 083691b392b1f9556dd64787f46e7536e7d2f7c3 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 5 Sep 2011 14:37:56 +0200 Subject: [PATCH 035/144] [IMP] ir.values: improve security: users can only write to their personal defaults Administrator access is required to set defaults for everybody, as well as to alter the action bindings. bzr revid: odo@openerp.com-20110905123756-oqum5k2pnbyoa11r --- openerp/addons/base/security/base_security.xml | 7 +++++++ openerp/addons/base/security/ir.model.access.csv | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/security/base_security.xml b/openerp/addons/base/security/base_security.xml index 00aca6613d6..fe3a10fdaed 100644 --- a/openerp/addons/base/security/base_security.xml +++ b/openerp/addons/base/security/base_security.xml @@ -65,6 +65,13 @@ [('company_id','child_of',[user.company_id.id])] + + Defaults: alter personal values only + + [('key','=','default'),('user_id','=',user.id)] + + +
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index 8949052f89b..6460c06d1f7 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -38,8 +38,7 @@ "access_ir_ui_view_custom_group_user","ir_ui_view_custom_group_user","model_ir_ui_view_custom",,1,0,0,0 "access_ir_ui_view_custom_group_system","ir_ui_view_custom_group_system","model_ir_ui_view_custom","group_system",1,1,1,1 "access_ir_ui_view_sc_group_user","ir_ui_view_sc group_user","model_ir_ui_view_sc",,1,1,1,1 -"access_ir_values_group_erp_manager","ir_values group_erp_manager","model_ir_values","group_erp_manager",1,1,1,1 -"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,0,1,0 +"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,1,1,1 "access_res_company_group_erp_manager","res_company group_erp_manager","model_res_company","group_erp_manager",1,1,1,1 "access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0 "access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0 From e333a2eb2e81763cb532b3cd514a80892f185fa1 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 5 Sep 2011 16:10:58 +0200 Subject: [PATCH 036/144] [IMP] ir.values: improved defaults doc + removed `object` column completely The `object` column actually directly depended on the value of the `key` column, so it can be totally removed with no side-effects. Docstrings updated following review comments. bzr revid: odo@openerp.com-20110905141058-xa01o77l1rto6hg9 --- openerp/addons/base/ir/ir.xml | 4 +- openerp/addons/base/ir/ir_values.py | 73 +++++++++++++++++++---------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index 3f9c48b467f..3ab0bb3f167 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -93,7 +93,7 @@ tree,form [('key','=','action')] - {'default_object':1,'default_key':'action'} + {'default_key':'action'} @@ -116,7 +116,7 @@ tree,form [('key','=','default')] - {'default_object':0,'default_key':'default','default_key2':''} + {'default_key':'default','default_key2':''} diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index ca2973f948e..2eb7b97d18a 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -78,8 +78,8 @@ class ir_values(osv.osv): all records). The content of the entry is defined by the ``value`` column, which may either - contain an arbitrary value (for default values - when the ``object`` column - is False), or a reference string defining the action that should be executed. + contain an arbitrary value, or a reference string defining the action that + should be executed. .. rubric:: Usage: default values @@ -102,14 +102,15 @@ class ir_values(osv.osv): def _value_unpickle(self, cursor, user, ids, name, arg, context=None): res = {} - for report in self.browse(cursor, user, ids, context=context): - value = report[name[:-9]] - if not report.object and value: + for record in self.browse(cursor, user, ids, context=context): + value = record[name[:-9]] + if record.key == 'default' and value: + # default values are pickled on the fly try: value = str(pickle.loads(value)) except Exception: pass - res[report.id] = value + res[record.id] = value return res def _value_pickle(self, cursor, user, id, name, value, arg, context=None): @@ -118,7 +119,9 @@ class ir_values(osv.osv): ctx = context.copy() if self.CONCURRENCY_CHECK_FIELD in ctx: del ctx[self.CONCURRENCY_CHECK_FIELD] - if not self.browse(cursor, user, id, context=context).object: + record = self.browse(cursor, user, id, context=context) + if record.key == 'default': + # default values are pickled on the fly value = pickle.dumps(value) self.write(cursor, user, id, {name[:-9]: value}, context=ctx) @@ -136,11 +139,6 @@ class ir_values(osv.osv): 'value': {'value_unpickle': act.type+','+str(act.id)} } - def onchange_key(self, cr, uid, ids, key, context=None): - return { - 'value': {'object': key == 'action'} - } - _columns = { 'name': fields.char('Name', size=128, required=True), 'model': fields.char('Model Name', size=128, select=True, required=True, @@ -160,7 +158,6 @@ class ir_values(osv.osv): 'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle, type='text', string='Default value or action reference'), - 'object': fields.boolean('No value serialization', help="Should be enabled when Type is Action"), 'key': fields.selection([('action','Action'),('default','Default')], 'Type', size=128, select=True, required=True, help="- Action: an action attached to one slot of the given model\n" @@ -184,7 +181,6 @@ class ir_values(osv.osv): _defaults = { 'key': 'action', 'key2': 'tree_but_open', - 'object': True, } def _auto_init(self, cr, context=None): @@ -195,7 +191,17 @@ class ir_values(osv.osv): def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False): """Defines a default value for the given model and field_name. Any previous - default will be replaced and lost in the process. + default for the same scope (model, field_name, value, for_all_users, company_id, condition) + will be replaced and lost in the process. + + Defaults can be later retrieved via :meth:`~.get_defaults`, which will return + the highest priority default for any given field. Defaults that are more specific + have a higher priority, in the following order (highest to lowest): + + * specific to user and company + * specific to user only + * specific to company only + * global to everyone :param string model: model name :param string field_name: field name to which the default applies @@ -208,7 +214,13 @@ class ir_values(osv.osv): is passed, the current user's company will be used. :param string condition: optional condition specification that can be used to restrict the applicability of the default values - (e.g. based on another field's value) + (e.g. based on another field's value). This is an + opaque string as far as the API is concerned, but client + stacks typically use single-field conditions in the + form ``'key=stringified_value'``. + (Currently, the condition is trimmed to 200 characters, + so values that share the same first 200 characters always + match) :return: id of the newly created ir.values entry """ if isinstance(value, unicode): @@ -233,7 +245,6 @@ class ir_values(osv.osv): 'name': field_name, 'value': pickle.dumps(value), 'model': model, - 'object': False, 'key': 'default', 'key2': condition and condition[:200], 'user_id': False if for_all_users else uid, @@ -241,18 +252,29 @@ class ir_values(osv.osv): }) def get_defaults(self, cr, uid, model, condition=False): - """Returns any user-defined values registered for the given model. - This is not field-specific, but an optional ``condition`` can be - provided to query for default values that should be applied only - when the given condition is met (usually another field's value). - Defaults that have been set for the user herself will have higher - priority that those that have been set for everyone - (see :meth:`~.set_default`). + """Returns any default values that are defined for the current model and user, + (and match ``condition``, if specified), previously registered via + :meth:`~.set_default`. + + Defaults are global to a model, not field-specific, but an optional + ``condition`` can be provided to restrict matching default values + to those that were defined for the same condition (usually based + on another field's value). + + Default values also have priorities depending on whom they apply + to: only the highest priority value will be returned for any + field. See :meth:`~.set_default` for more details. :param string model: model name :param string condition: optional condition specification that can be used to restrict the applicability of the default values - (e.g. based on another field's value) + (e.g. based on another field's value). This is an + opaque string as far as the API is concerned, but client + stacks typically use single-field conditions in the + form ``'key=stringified_value'``. + (Currently, the condition is trimmed to 200 characters, + so values that share the same first 200 characters always + match) :return: list of default values tuples of the form ``(id, field_name, value)`` (``id`` is the ID of the default entry, usually irrelevant) """ @@ -317,7 +339,6 @@ class ir_values(osv.osv): return self.create(cr, uid, { 'key': 'action', 'key2': action_slot, - 'object': True, 'model': model, 'res_id': res_id, 'name': name, From 3ba75e5d50b95f6ba01400afcf1c31ce3ee0f10e Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Thu, 8 Sep 2011 15:15:46 +0530 Subject: [PATCH 037/144] [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 fbb6e5e56aaf36ff652edbec0181a0fcb95a3601 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 8 Sep 2011 13:38:33 +0200 Subject: [PATCH 038/144] [FIX] ir.values.get_defaults: typo in SQL query - spotted by Naresh, thanks! bzr revid: odo@openerp.com-20110908113833-z2cgkj093940usys --- openerp/addons/base/ir/ir_values.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index 2eb7b97d18a..d908c2096b3 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -284,8 +284,8 @@ class ir_values(osv.osv): LEFT JOIN res_users u ON (v.user_id = u.id) WHERE v.key = %%s AND v.model = %%s AND (v.user_id = %%s OR v.user_id IS NULL) - AND (u.company_id IS NULL OR - u.company_id = + AND (v.company_id IS NULL OR + v.company_id = (SELECT company_id from res_users where id = %%s) ) %s From 7ce7230c4010cf5e7099bbd7c34b4cba19b54388 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Tue, 13 Sep 2011 18:24:56 +0530 Subject: [PATCH 039/144] [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 040/144] [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 041/144] [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 8a9c0dbad7323509b07cda605aed84bf51584753 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 13:12:13 +0200 Subject: [PATCH 042/144] [IMP] bulk-update fields_view_get result via dict.update instead of a bunch of __setitem__ bzr revid: xmo@openerp.com-20110915111213-zigij077k9ss9p6e --- openerp/osv/orm.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7efe2272754..315d2bce0de 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1954,14 +1954,13 @@ class orm_template(object): # if a view was found if sql_res: - result['type'] = sql_res['type'] - result['view_id'] = sql_res['id'] - source = etree.fromstring(encode(sql_res['arch'])) - result['arch'] = apply_view_inheritance(source, result['view_id']) - - result['name'] = sql_res['name'] - result['field_parent'] = sql_res['field_parent'] or False + result.update( + arch=apply_view_inheritance(source, sql_res['id']), + type=sql_res['type'], + view_id=sql_res['id'], + name=sql_res['name'], + field_parent=sql_res['field_parent'] or False) else: # otherwise, build some kind of default view @@ -1994,10 +1993,11 @@ class orm_template(object): else: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) - result['arch'] = etree.fromstring(encode(xml)) - result['name'] = 'default' - result['field_parent'] = False - result['view_id'] = 0 + result.update( + arch=etree.fromstring(encode(xml)), + name='default', + field_parent=False, + view_id=0) if parent_view_model != self._name: ctx = context.copy() From 734c1a43a0e4a208185ab967cc1bf087a2d15e5d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 13:12:21 +0200 Subject: [PATCH 043/144] [REF] extract the two inlined default view generators left into fields_view_get into their own methods follow the preexisting __get_default_$name_view bzr revid: xmo@openerp.com-20110915111221-20ktl1bi2qne5wz7 --- openerp/osv/orm.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 315d2bce0de..960217b4de8 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1689,6 +1689,28 @@ class orm_template(object): raise except_orm('View error', msg) return arch, fields + def __get_default_form_view(self, cr, user, context=None): + # TODO it seems fields_get can be replaced by _all_columns (no need for translation) + res = self.fields_get(cr, user, context=context) + xml = ' ' \ + '
' % (self._description,) + for x in res: + if res[x]['type'] not in ('one2many', 'many2many'): + xml += '' % (x,) + if res[x]['type'] == 'text': + xml += "" + xml += "" + return xml + + def __get_default_tree_view(self, cr, user, context=None): + _rec_name = self._rec_name + if _rec_name not in self._columns: + _rec_name = self._columns.keys()[0] + xml = '' \ + '' \ + % (self._description, _rec_name) + return xml + def __get_default_calendar_view(self): """Generate a default calendar view (For internal use only). """ @@ -1965,24 +1987,10 @@ class orm_template(object): # otherwise, build some kind of default view if view_type == 'form': - # TODO it seems fields_get can be replaced by _all_columns (no need for translation) - res = self.fields_get(cr, user, context=context) - xml = ' ' \ - '
' % (self._description,) - for x in res: - if res[x]['type'] not in ('one2many', 'many2many'): - xml += '' % (x,) - if res[x]['type'] == 'text': - xml += "" - xml += "" + xml = self.__get_default_form_view(cr, user, context) elif view_type == 'tree': - _rec_name = self._rec_name - if _rec_name not in self._columns: - _rec_name = self._columns.keys()[0] - xml = '' \ - '' \ - % (self._description, _rec_name) + xml = self.__get_default_tree_view(cr, user, context) elif view_type == 'calendar': xml = self.__get_default_calendar_view() From ebb2072207c23fe1ad12310207a72ac27e2a754e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 13:12:55 +0200 Subject: [PATCH 044/144] [IMP] regroup all __get_default_$name_view calls into a single parametric getattr Also rename __get_default_ to _get_default_ so it can be found by getattr bzr revid: xmo@openerp.com-20110915111255-1i0y555er3nwtkzg --- openerp/osv/orm.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 960217b4de8..773feb5808d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1689,7 +1689,7 @@ class orm_template(object): raise except_orm('View error', msg) return arch, fields - def __get_default_form_view(self, cr, user, context=None): + def _get_default_form_view(self, cr, user, context=None): # TODO it seems fields_get can be replaced by _all_columns (no need for translation) res = self.fields_get(cr, user, context=context) xml = ' ' \ @@ -1702,7 +1702,7 @@ class orm_template(object): xml += "" return xml - def __get_default_tree_view(self, cr, user, context=None): + def _get_default_tree_view(self, cr, user, context=None): _rec_name = self._rec_name if _rec_name not in self._columns: _rec_name = self._columns.keys()[0] @@ -1711,7 +1711,7 @@ class orm_template(object): % (self._description, _rec_name) return xml - def __get_default_calendar_view(self): + def _get_default_calendar_view(self, cr, user, context=None): """Generate a default calendar view (For internal use only). """ # TODO could return an etree instead of a string @@ -1758,7 +1758,7 @@ class orm_template(object): return arch - def __get_default_search_view(self, cr, uid, context=None): + def _get_default_search_view(self, cr, uid, context=None): form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) @@ -1984,23 +1984,14 @@ class orm_template(object): name=sql_res['name'], field_parent=sql_res['field_parent'] or False) else: - # otherwise, build some kind of default view - if view_type == 'form': - xml = self.__get_default_form_view(cr, user, context) - - elif view_type == 'tree': - xml = self.__get_default_tree_view(cr, user, context) - - elif view_type == 'calendar': - xml = self.__get_default_calendar_view() - - elif view_type == 'search': - xml = self.__get_default_search_view(cr, user, context) - - else: + try: + xml = getattr(self, '_get_default_%s_view' % view_type)( + cr, user, context) + except AttributeError: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) + result.update( arch=etree.fromstring(encode(xml)), name='default', From 005e2b87a57bba82cec4b73b26ce8af6392ffc4d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:04:29 +0200 Subject: [PATCH 045/144] [IMP] etree-ify default view generations * Search already used etree internally, remove serialization to string * form and tree were easy to convert * pull down fvg's parsing (etree.fromstring) into calendar generator before converting it bzr revid: xmo@openerp.com-20110915120429-syz190w61iq52rel --- openerp/osv/orm.py | 70 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 773feb5808d..c60c440af24 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1690,32 +1690,53 @@ class orm_template(object): return arch, fields def _get_default_form_view(self, cr, user, context=None): + """ Generates a default single-line form view using all fields + of the current model except the m2m and o2m ones. + + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: a form view as an lxml document + :rtype: etree._Element + """ + view = etree.Element('form', string=self._description) # TODO it seems fields_get can be replaced by _all_columns (no need for translation) - res = self.fields_get(cr, user, context=context) - xml = ' ' \ - '
' % (self._description,) - for x in res: - if res[x]['type'] not in ('one2many', 'many2many'): - xml += '' % (x,) - if res[x]['type'] == 'text': - xml += "" - xml += "" - return xml + for field, descriptor in self.fields_get(cr, user, context=context).iteritems(): + if descriptor['type'] in ('one2many', 'many2many'): + continue + etree.SubElement(view, 'field', name=field) + if descriptor['type'] == 'text': + etree.SubElement(view, 'newline') + return view def _get_default_tree_view(self, cr, user, context=None): + """ Generates a single-field tree view, using _rec_name if + it's one of the columns or the first column it finds otherwise + + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: a tree view as an lxml document + :rtype: etree._Element + """ _rec_name = self._rec_name if _rec_name not in self._columns: _rec_name = self._columns.keys()[0] - xml = '' \ - '' \ - % (self._description, _rec_name) - return xml + + view = etree.Element('tree', string=self._description) + etree.SubElement(view, 'field', name=_rec_name) + return view def _get_default_calendar_view(self, cr, user, context=None): - """Generate a default calendar view (For internal use only). + """ Generates a default calendar view by trying to infer + calendar fields from a number of pre-set attribute names + + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: a calendar view + :rtype: etree._Element """ - # TODO could return an etree instead of a string - arch = ('\n' '\n' '') % (self._rec_name) - return arch + return etree.fromstring(arch.encode('utf-8')) def _get_default_search_view(self, cr, uid, context=None): + """ + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: an lxml document of the view + :rtype: etree._Element + """ form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) @@ -1783,7 +1811,7 @@ class orm_template(object): field_group.append(etree.Element("field", attrib={'name': field_name})) #TODO tostring can be removed as fromstring is call directly after... - return etree.tostring(search_view, encoding="utf-8").replace('\t', '') + return search_view # # if view_id, view_type is not required @@ -1986,14 +2014,14 @@ class orm_template(object): else: # otherwise, build some kind of default view try: - xml = getattr(self, '_get_default_%s_view' % view_type)( + view = getattr(self, '_get_default_%s_view' % view_type)( cr, user, context) except AttributeError: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) result.update( - arch=etree.fromstring(encode(xml)), + arch=view, name='default', field_parent=False, view_id=0) From 4cdda26b6c959d2c6c7323dd9e173f352e83c788 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:11:29 +0200 Subject: [PATCH 046/144] [IMP] convert default calendar view generation (from fields_view_get) to lxml.etree bzr revid: xmo@openerp.com-20110915121129-fcdq8e0rix2ocpwt --- openerp/osv/orm.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index c60c440af24..e8556dbc9d4 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1737,8 +1737,8 @@ class orm_template(object): :returns: a calendar view :rtype: etree._Element """ - arch = ('\n' - '\n' - ' \n' - '') % (self._rec_name) - - return etree.fromstring(arch.encode('utf-8')) + return view def _get_default_search_view(self, cr, uid, context=None): """ From 74e98f422043bec3b2b10067b1e5665eeb9f9aff Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:15:44 +0200 Subject: [PATCH 047/144] [ADD] error when neither date_stop nor date_delay can be generated for a default calendar view bzr revid: xmo@openerp.com-20110915121544-eq3mumulb0qp9yfa --- openerp/osv/orm.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index e8556dbc9d4..1d9fb9ccfa0 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1761,12 +1761,15 @@ class orm_template(object): if dt_stop in self._columns: view.set('date_stop', dt_stop) break - - if not view.get('date_stop'): + if 'date_stop' not in view.attrib: for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]: if dt_delay in self._columns: view.set('date_delay', dt_delay) break + if 'date_delay' not in view.attrib: + raise except_orm( + _('Invalid Object Architecture!'), + _("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay" % (self._name))) return view From e878560c7450f4f3bbf7b43d93aa04214218a07a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:24:43 +0200 Subject: [PATCH 048/144] [ADD] small helper to calendar generation code, in order to make flow clearer bzr revid: xmo@openerp.com-20110915122443-hvk1u2372ppd77m1 --- openerp/osv/orm.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 1d9fb9ccfa0..c0ca7e9d696 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1737,6 +1737,19 @@ class orm_template(object): :returns: a calendar view :rtype: etree._Element """ + def set_first_of(seq, in_, to): + """Sets the first value of ``set`` also found in ``in`` to + the ``to`` attribute of the view being closed over. + + Returns whether it's found a suitable value (and set it on + the attribute) or not + """ + for item in seq: + if item in in_: + view.set(key, item) + return True + return False + view = etree.Element('calendar', string=self._description) etree.SubElement(view, 'field', name=self._rec_name) @@ -1752,21 +1765,13 @@ class orm_template(object): raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!")) view.set('date_start', self._date_name) - for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]: - if color in self._columns: - view.set('color', color) - break + set_first_of(["user_id", "partner_id", "x_user_id", "x_partner_id"], + self._columns, 'color') - for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]: - if dt_stop in self._columns: - view.set('date_stop', dt_stop) - break - if 'date_stop' not in view.attrib: - for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]: - if dt_delay in self._columns: - view.set('date_delay', dt_delay) - break - if 'date_delay' not in view.attrib: + if not set_first_of(["date_stop", "date_end", "x_date_stop", "x_date_end"], + self._columns, 'date_stop'): + if not set_first_of(["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"], + self._columns, 'date_delay'): raise except_orm( _('Invalid Object Architecture!'), _("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay" % (self._name))) From eeb31efa882408be3bbdb98afb928bd8a297edb7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:28:34 +0200 Subject: [PATCH 049/144] [IMP] automatic search view generation: comprehensions are awesome bzr revid: xmo@openerp.com-20110915122834-0ec4dkaznbhtc0kc --- openerp/osv/orm.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index c0ca7e9d696..48b607273de 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1789,12 +1789,11 @@ class orm_template(object): form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) - fields_to_search = set() # TODO it seems _all_columns could be used instead of fields_get (no need for translated fields info) fields = self.fields_get(cr, uid, context=context) - for field in fields: - if fields[field].get('select'): - fields_to_search.add(field) + fields_to_search = set( + field for field, descriptor in fields.iteritems() + if descriptor.get('select')) for view in (form_view, tree_view): view_root = etree.fromstring(view['arch']) # Only care about select=1 in xpath below, because select=2 is covered From 7bb856b834b6ba30f406459d0b36b34dcd351973 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:31:55 +0200 Subject: [PATCH 050/144] [IMP] search view generation: update fields set in place instead of creating a new set via union bzr revid: xmo@openerp.com-20110915123155-a5ht8nhfp9kq0kst --- openerp/osv/orm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 48b607273de..625247d464f 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1794,11 +1794,12 @@ class orm_template(object): fields_to_search = set( field for field, descriptor in fields.iteritems() if descriptor.get('select')) + for view in (form_view, tree_view): view_root = etree.fromstring(view['arch']) # Only care about select=1 in xpath below, because select=2 is covered # by the custom advanced search in clients - fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name")) + fields_to_search.update(view_root.xpath("//field[@select=1]/@name")) tree_view_root = view_root # as provided by loop above search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")}) From a09be0e9b94626d45db20272ba9ecab469aa8c66 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:37:07 +0200 Subject: [PATCH 051/144] [IMP] simplify search view generation: rely on etree.SubElement to graft new nodes to the search view bzr revid: xmo@openerp.com-20110915123707-901mkfkz8e6ikre4 --- openerp/osv/orm.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 625247d464f..8070d56d9e4 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1802,14 +1802,12 @@ class orm_template(object): fields_to_search.update(view_root.xpath("//field[@select=1]/@name")) tree_view_root = view_root # as provided by loop above - search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")}) - field_group = etree.Element("group") - search_view.append(field_group) + search_view = etree.Element("search", string=tree_view_root.get("string", "")) + field_group = etree.SubElement(search_view, "group") for field_name in fields_to_search: - field_group.append(etree.Element("field", attrib={'name': field_name})) + etree.SubElement(field_group, "field", name=field_name)) - #TODO tostring can be removed as fromstring is call directly after... return search_view # From 662c8842f4eef498feb6711c87a3fdd2c75a3611 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:47:41 +0200 Subject: [PATCH 052/144] [IMP] fields_view_get: listcomps are still awesome also use itertools.chain to iterate over multiple lists in sequence, instead of concatenating them bzr revid: xmo@openerp.com-20110915124741-v5uneqmt2c3v6cmp --- openerp/osv/orm.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8070d56d9e4..c08fe1e2357 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -44,6 +44,7 @@ import calendar import copy import datetime +import itertools import logging import warnings import operator @@ -2052,13 +2053,11 @@ class orm_template(object): resrelate = ir_values_obj.get(cr, user, 'action', 'client_action_relate', [(self._name, False)], False, context) - resprint = map(clean, resprint) - resaction = map(clean, resaction) - resaction = filter(lambda x: not x.get('multi', False), resaction) - resprint = filter(lambda x: not x.get('multi', False), resprint) + resaction = [clean(action) for action in resaction if not action.get('multi')] + resprint = [clean(print_) for print_ in resprint if not print_.get('multi')] resrelate = map(lambda x: x[2], resrelate) - for x in resprint + resaction + resrelate: + for x in itertools.chain(resprint, resaction, resrelate): x['string'] = x['name'] result['toolbar'] = { From 52c5e2971031f0ada8497e631ac1cdb2f9e23d7e Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 16 Sep 2011 15:23:28 +0530 Subject: [PATCH 053/144] [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 054/144] [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 d57135eaefa4ff9bc0e9c80838a2a3b163e92f4b Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 16 Sep 2011 16:02:52 +0200 Subject: [PATCH 055/144] [IMP] fetchmail: Add a new type for the incoming server and generate a zip file with the script of the mailgateway and the right configuration bzr revid: stw@openerp.com-20110916140252-movu1cxjubt0bc44 --- addons/fetchmail/fetchmail.py | 63 +++++++++++++++++++++++++++-- addons/fetchmail/fetchmail_view.xml | 16 ++++++-- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py index b7460965c3b..1df9ba998b1 100644 --- a/addons/fetchmail/fetchmail.py +++ b/addons/fetchmail/fetchmail.py @@ -25,6 +25,14 @@ from imaplib import IMAP4 from imaplib import IMAP4_SSL from poplib import POP3 from poplib import POP3_SSL +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +import zipfile +import base64 +import addons import netsvc from osv import osv, fields @@ -46,11 +54,12 @@ class fetchmail_server(osv.osv): ('draft', 'Not Confirmed'), ('done', 'Confirmed'), ], 'State', select=True, readonly=True), - 'server' : fields.char('Server Name', size=256, required=True, readonly=True, help="Hostname or IP of the mail server", states={'draft':[('readonly', False)]}), - 'port' : fields.integer('Port', required=True, readonly=True, states={'draft':[('readonly', False)]}), + 'server' : fields.char('Server Name', size=256, readonly=True, help="Hostname or IP of the mail server", states={'draft':[('readonly', False)]}), + 'port' : fields.integer('Port', readonly=True, states={'draft':[('readonly', False)]}), 'type':fields.selection([ ('pop', 'POP Server'), ('imap', 'IMAP Server'), + ('private_mta', 'Private Mail Server'), ], 'Server Type', select=True, required=True, readonly=False), 'is_ssl':fields.boolean('SSL/TLS', help="Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"), 'attach':fields.boolean('Keep Attachments', help="Whether attachments should be downloaded. " @@ -58,7 +67,8 @@ class fetchmail_server(osv.osv): 'original':fields.boolean('Keep Original', help="Whether a full original copy of each email should be kept for reference" "and attached to each processed message. This will usually double the size of your message database."), 'date': fields.datetime('Last Fetch Date', readonly=True), - 'user' : fields.char('Username', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}), + 'user' : fields.char('Username', size=256, readonly=True, states={'draft':[('readonly', False)]}), + 'user_id' : fields.many2one('res.users', 'User', states={'draft' : [('readonly', False)]}), 'password' : fields.char('Password', size=1024, required=True, readonly=True, states={'draft':[('readonly', False)]}), 'note': fields.text('Description'), 'action_id':fields.many2one('ir.actions.server', 'Server Action', help="Optional custom server action to trigger for each incoming mail, " @@ -70,6 +80,9 @@ class fetchmail_server(osv.osv): 'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, " "lower values mean higher priority"), 'message_ids': fields.one2many('mail.message', 'fetchmail_server_id', 'Messages', readonly=True), + 'configuration' : fields.text('Configuration', readonly=True), + 'archive' : fields.binary('Archive', readonly=True), + 'archive_filename' : fields.char('Filename', size=128, readonly=True), } _defaults = { 'state': "draft", @@ -78,13 +91,55 @@ class fetchmail_server(osv.osv): 'attach': True, } + def generate_configuration(self, cr, uid, ids, context=None): + this = self.browse(cr, uid, ids[0], context=context) + + configuration = """You should use this command line with your Mail Transport Agent (MTA) +openerp_mailgate.py -u %(user_id)d -p PASSWORD -o %(model)s -d %(database)s --host=HOSTNAME --port=PORT + """ % {'user_id' : this.user_id.id, + 'model' : this.object_id.model, + 'database' : cr.dbname, + } + + values = dict( + configuration=configuration, + ) + + try: + archive_io = StringIO.StringIO() + archive = zipfile.ZipFile(archive_io, 'w') + content = open( + addons.get_module_resource('mail', 'scripts', 'openerp_mailgate', 'openerp_mailgate.py'), + 'rb' + ).read() + archive.writestr('openerp_mailgate.py', content) + archive.writestr('README', configuration) + archive_io.seek(0) + + values.update( + archive_filename = 'openerp-mailgate.zip', + archive = base64.encodestring(archive_io.getvalue()) + ) + + finally: + archive.close() + archive_io.close() + + return this.write(values) + def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False): port = 0 + values = {} if server_type == 'pop': port = ssl and 995 or 110 elif server_type == 'imap': port = ssl and 993 or 143 - return {'value':{'port':port}} + else: + values['server'] = '' + + values['port'] = port + + return {'value':values} def set_draft(self, cr, uid, ids, context=None): self.write(cr, uid, ids , {'state':'draft'}) diff --git a/addons/fetchmail/fetchmail_view.xml b/addons/fetchmail/fetchmail_view.xml index 05f62f4b9fb..ceb28284a6c 100644 --- a/addons/fetchmail/fetchmail_view.xml +++ b/addons/fetchmail/fetchmail_view.xml @@ -32,15 +32,16 @@ - + - - + + - + + @@ -50,6 +51,13 @@ + + + +