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/304] [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/304] [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/304] [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/304] [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/304] [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/304] [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/304] [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/304] [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/304] [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 9cb3205a6432ccc7ba2458416786774b39bc50d6 Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Fri, 3 Jun 2011 15:25:26 +0530 Subject: [PATCH 010/304] [FIX] fix problem of search multiple conditions O2M with active=False then not work. lp bug: https://launchpad.net/bugs/711919 fixed bzr revid: ysa@tinyerp.com-20110603095526-s26wgkahwc1d6zfd --- openerp/osv/expression.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 1922d5e8388..4c74a11506a 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -108,6 +108,10 @@ class expression(object): self.__main_table = table self.__all_tables.add(table) + active = False + for exp in self.__exp: + if exp[0] == 'active': + active = exp i = -1 while i + 1','<=','>='] ) and 'in' or operator - + dict_op = {'not in':'!=','in':'=','=':'in','!=':'not in','<>':'not in'} if isinstance(right,tuple): right = list(right) @@ -319,7 +326,7 @@ class expression(object): elif isinstance(right,(list,tuple)): m2o_str = True for ele in right: - if not isinstance(ele, basestring): + if not isinstance(ele, basestring): m2o_str = False break elif right == []: @@ -335,7 +342,7 @@ class expression(object): new_op = '!=' #Is it ok to put 'left' and not 'id' ? self.__exp[i] = (left,new_op,False) - + if m2o_str: self.__exp[i] = _get_expression(field_obj,cr, uid, left, right, operator, context=context) else: From 2a9a5e2a0e3fdf1bc104e1a5c923baf85f1d963b Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Fri, 3 Jun 2011 15:42:17 +0530 Subject: [PATCH 011/304] Improve code. bzr revid: ysa@tinyerp.com-20110603101217-v69rcqtpcfics925 --- openerp/osv/expression.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 4c74a11506a..8fb85ce7612 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -154,10 +154,10 @@ class expression(object): # Making search easier when there is a left operand as field.o2m or field.m2m if field._type in ['many2many','one2many']: right = field_obj.search(cr, uid, [(fargs[1], operator, right)], context=context) + domain = [(fargs[0],'in', right)] if active: - right1 = table.search(cr, uid, [(fargs[0],'in', right),active], context=context) - else: - right1 = table.search(cr, uid, [(fargs[0],'in', right)], context=context) + domain.append(active) + right1 = table.search(cr, uid, domain, context=context) if right1 == []: self.__exp[i] = ( 'id', '=', 0 ) else: From 332d385390a48cd82670182c6b76e92bbf35397e Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Fri, 3 Jun 2011 16:15:39 +0530 Subject: [PATCH 012/304] [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 013/304] [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 110483e93d4a6cf1f461002be93a607b4fb1ec65 Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Wed, 8 Jun 2011 16:11:28 +0530 Subject: [PATCH 014/304] [FIX] fix problem of ir.property created if same value exists then record not create in ir.property object. lp bug: https://launchpad.net/bugs/726592 fixed bzr revid: ysa@tinyerp.com-20110608104128-7mdykmk0ovqenz6u --- openerp/osv/fields.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 306e7d007f3..ec1845344ab 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1047,7 +1047,14 @@ class property(function): default_val = self._get_default(obj, cr, uid, prop_name, context) - if id_val is not default_val: + property_create = False + if hasattr(default_val, '_id'): + if default_val._id != id_val: + property_create = True + elif id_val != default_val: + property_create = True + + if property_create: def_id = self._field_get(cr, uid, obj._name, prop_name) company = obj.pool.get('res.company') cid = company._company_default_get(cr, uid, obj._name, def_id, From 4203df9c260fda5bcc5f2e60504483d1078b770c Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Mon, 13 Jun 2011 11:40:41 +0530 Subject: [PATCH 015/304] [IMP] add yaml test case. bzr revid: ysa@tinyerp.com-20110613061041-15edtf67mj0oa8hq --- .../addons/base/test/test_osv_expression.yml | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/test/test_osv_expression.yml b/openerp/addons/base/test/test_osv_expression.yml index 4916fe06203..1f76a210293 100644 --- a/openerp/addons/base/test/test_osv_expression.yml +++ b/openerp/addons/base/test/test_osv_expression.yml @@ -40,7 +40,7 @@ - !assert {model: res.partner, search: "[('category_id', '=', False)]"}: - category_id in (False, None, []) -- +- Filtering on invalid value across x2many relationship should return an empty set - !assert {model: res.partner, search: "[('address.city','=','foo')]", count: 0, string: "Searching for address.city = foo should give empty results"} @@ -110,4 +110,21 @@ res_ids = self.search(cr, uid, [('company_id.partner_id', 'not in', [])]) res_ids.sort() assert res_ids == all_ids, "Searching against empty set failed, returns %r" % res_ids - +- + Testing for Many2Many field with category supplier and active=False +- + !python {model: res.partner }: | + vals = {'category_id': [(6, 0, [ref("base.res_partner_category_8")])], + 'name': 'OpenERP Test', + 'active': False, + 'address': [(0, 0, {'country_id': ref("base.be")})] + } + self.create(cr, uid, vals, context=context) + res_ids = self.search(cr, uid, [('category_id', 'ilike', 'supplier'), ('active', '=', False)]) + assert len(res_ids) != 0, "Record not Found with category supplier and active False." +- + Testing for One2Many field with country Belgium and active=False +- + !python {model: res.partner }: | + res_ids = self.search(cr, uid, [('address.country_id','=','Belgium'),('active','=',False)]) + assert len(res_ids) != 0, "Record not Found with country Belgium and active False." From ab62f7b7802571f56a56fce7389b777d6fee2583 Mon Sep 17 00:00:00 2001 From: "Amit Parmar (OpenERP)" Date: Tue, 21 Jun 2011 18:46:32 +0530 Subject: [PATCH 016/304] [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 017/304] [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 018/304] [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 019/304] [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 020/304] [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 021/304] [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 022/304] [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 023/304] [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 024/304] [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 025/304] [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 026/304] [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 027/304] [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 028/304] [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 029/304] [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 030/304] [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 031/304] [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 032/304] [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 033/304] [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 034/304] [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 035/304] [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 036/304] [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 037/304] [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 038/304] [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 039/304] [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 040/304] [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 041/304] [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 042/304] [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 09b63ae6ea0dfe2e173218097dde865d791ac529 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 16:15:41 +0200 Subject: [PATCH 043/304] [FIX] don't *eval* field names to get the field type associated with a function field bzr revid: xmo@openerp.com-20110909141541-p172s7jqu597sjc9 --- openerp/osv/orm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index b87672c012d..341f14e00e2 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -514,9 +514,9 @@ def get_pg_type(f): f_type = ('int4', 'INTEGER') else: f_type = ('varchar', 'VARCHAR(%d)' % f_size) - elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict: - t = eval('fields.'+(f._type), globals()) - f_type = (type_dict[t], type_dict[t]) + elif isinstance(f, fields.function) and getattr(fields, f._type, None) in type_dict: + t = type_dict[getattr(fields, f._type)] + f_type = (t, t) elif isinstance(f, fields.function) and f._type == 'float': if f.digits: f_type = ('numeric', 'NUMERIC') From cf44b49c939c7209111fea41b9a506d21b96dbee Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 16:33:49 +0200 Subject: [PATCH 044/304] [IMP] extract VARCHAR typing, if no size is provided (or the size is 0) don't put a limit on the varchar bzr revid: xmo@openerp.com-20110909143349-5sykc4lpsn3rxf5w --- openerp/osv/orm.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 341f14e00e2..8041628626d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -475,6 +475,18 @@ class browse_record(object): __repr__ = __str__ +def pg_varchar(size=0): + """ Returns the VARCHAR declaration for the provided size: + + * If no size (or an empty size is provided) return an 'infinite' VARCHAR + * Otherwise return a VARCHAR(n) + + :type int size: varchar size, optional + :rtype: str + """ + if size: + return 'VARCHAR(%d)' % size + return 'VARCHAR' def get_pg_type(f): """ @@ -501,19 +513,19 @@ def get_pg_type(f): else: f_type = ('float8', 'DOUBLE PRECISION') elif isinstance(f, (fields.char, fields.reference)): - f_type = ('varchar', 'VARCHAR(%d)' % (f.size,)) + f_type = ('varchar', pg_varchar(f.size)) elif isinstance(f, fields.selection): if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)): - f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16) + f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size) elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int): f_size = -1 else: - f_size = getattr(f, 'size', None) or 16 + f_size = getattr(f, 'size', None) if f_size == -1: f_type = ('int4', 'INTEGER') else: - f_type = ('varchar', 'VARCHAR(%d)' % f_size) + f_type = ('varchar', pg_varchar(f_size)) elif isinstance(f, fields.function) and getattr(fields, f._type, None) in type_dict: t = type_dict[getattr(fields, f._type)] f_type = (t, t) @@ -525,7 +537,7 @@ def get_pg_type(f): elif isinstance(f, fields.function) and f._type == 'selection': f_type = ('text', 'text') elif isinstance(f, fields.function) and f._type == 'char': - f_type = ('varchar', 'VARCHAR(%d)' % (f.size)) + f_type = ('varchar', pg_varchar(f.size)) else: logger = netsvc.Logger() logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f))) @@ -2838,7 +2850,7 @@ class orm(orm_template): if f_obj_type: ok = False casts = [ - ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)), + ('text', 'char', pg_varchar(f.size), '::%s' % pg_varchar(f.size), ('varchar', 'text', 'TEXT', ''), ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]), ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'), @@ -2848,8 +2860,8 @@ class orm(orm_template): ] if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size: cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k)) - cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size)) - cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size)) + cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, pg_varchar(f.size))) + cr.execute('UPDATE "%s" SET "%s"=temp_change_size::%s' % (self._table, k, pg_varchar(f.size))) cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,)) cr.commit() self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s", From 8668ad7ed3d9d2b406c179f1e111b2d63c875ad6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 16:52:13 +0200 Subject: [PATCH 045/304] [IMP] have function fields delegate their pg_type discovery to get_pg_type * add a type override to get_pg_type, so the function field can pass itself as another field type * special-case the 'selection' role, not sure it's correct but it was pretty special last time around (I guess since we don't have the field's values available we can't just do the type/size inference of get_pg_type) bzr revid: xmo@openerp.com-20110909145213-y172z8eyfnond43w --- openerp/osv/orm.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8041628626d..97c4ef89c60 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -488,11 +488,14 @@ def pg_varchar(size=0): return 'VARCHAR(%d)' % size return 'VARCHAR' -def get_pg_type(f): +def get_pg_type(f, type_override=None): """ - returns a tuple - (type returned by postgres when the column was created, type expression to create the column) + :param fields._column f: field to get a Postgres type for + :param type type_override: use the provided type for dispatching instead of the field's own type + :returns: (postgres_identification_type, postgres_type_specification) + :rtype: (str, str) """ + field_type = type_override or type(f) type_dict = { fields.boolean: 'bool', @@ -505,8 +508,8 @@ def get_pg_type(f): fields.binary: 'bytea', fields.many2one: 'int4', } - if type(f) in type_dict: - f_type = (type_dict[type(f)], type_dict[type(f)]) + if field_type in type_dict: + f_type = (type_dict[field_type], type_dict[field_type]) elif isinstance(f, fields.float): if f.digits: f_type = ('numeric', 'NUMERIC') @@ -526,21 +529,15 @@ def get_pg_type(f): f_type = ('int4', 'INTEGER') else: f_type = ('varchar', pg_varchar(f_size)) - elif isinstance(f, fields.function) and getattr(fields, f._type, None) in type_dict: - t = type_dict[getattr(fields, f._type)] - f_type = (t, t) - elif isinstance(f, fields.function) and f._type == 'float': - if f.digits: - f_type = ('numeric', 'NUMERIC') + + elif isinstance(f, fields.function): + if f._type = 'selection': + f_type = ('varchar', pg_varchar()) else: - f_type = ('float8', 'DOUBLE PRECISION') - elif isinstance(f, fields.function) and f._type == 'selection': - f_type = ('text', 'text') - elif isinstance(f, fields.function) and f._type == 'char': - f_type = ('varchar', pg_varchar(f.size)) + f_type = get_pg_type(f, getattr(fields, f._type)) else: logger = netsvc.Logger() - logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f))) + logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (field_type)) f_type = None return f_type From 7a3960f736436bb395e237153207c54214aa8dd2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 17:23:07 +0200 Subject: [PATCH 046/304] [IMP] remove size inference for selection lists with str|unicode value choices, simplify selection pg_type selection * if the first element of the choices list is an integer, use int4 * if the field has a size, use that size * else use an unrestricted varchar bzr revid: xmo@openerp.com-20110909152307-ralzt1g8l1b3e46x --- openerp/osv/orm.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 97c4ef89c60..2892a32c440 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -518,18 +518,10 @@ def get_pg_type(f, type_override=None): elif isinstance(f, (fields.char, fields.reference)): f_type = ('varchar', pg_varchar(f.size)) elif isinstance(f, fields.selection): - if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)): - f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size) - elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int): - f_size = -1 - else: - f_size = getattr(f, 'size', None) - - if f_size == -1: + if isinstance(f.selection, list) and isinstance(f.selection[0][0], int): f_type = ('int4', 'INTEGER') else: - f_type = ('varchar', pg_varchar(f_size)) - + f_type = ('varchar', pg_varchar(getattr(f, 'size', None))) elif isinstance(f, fields.function): if f._type = 'selection': f_type = ('varchar', pg_varchar()) From b9ed013504639a536b0d5c419507985e9bf9ded4 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 17:24:45 +0200 Subject: [PATCH 047/304] [IMP] use logging API in get_pg_types bzr revid: xmo@openerp.com-20110909152445-pol8ko3ckzc6p7s0 --- openerp/osv/orm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2892a32c440..692368cd44d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -528,8 +528,7 @@ def get_pg_type(f, type_override=None): else: f_type = get_pg_type(f, getattr(fields, f._type)) else: - logger = netsvc.Logger() - logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (field_type)) + logging.getLogger('orm').warn('%s type not supported!', field_type) f_type = None return f_type From f74c55182a5371c88f64af326cbbb3f56958fbb0 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 17:26:22 +0200 Subject: [PATCH 048/304] [IMP] lift mapping from field types to PGTYPES outside get_pg_type bzr revid: xmo@openerp.com-20110909152622-114y4401x6gw6a79 --- openerp/osv/orm.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 692368cd44d..f20b759f6f8 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -488,6 +488,17 @@ def pg_varchar(size=0): return 'VARCHAR(%d)' % size return 'VARCHAR' +FIELDS_TO_PGTYPES = { + fields.boolean: 'bool', + fields.integer: 'int4', + fields.integer_big: 'int8', + fields.text: 'text', + fields.date: 'date', + fields.time: 'time', + fields.datetime: 'timestamp', + fields.binary: 'bytea', + fields.many2one: 'int4', +} def get_pg_type(f, type_override=None): """ :param fields._column f: field to get a Postgres type for @@ -497,19 +508,8 @@ def get_pg_type(f, type_override=None): """ field_type = type_override or type(f) - type_dict = { - fields.boolean: 'bool', - fields.integer: 'int4', - fields.integer_big: 'int8', - fields.text: 'text', - fields.date: 'date', - fields.time: 'time', - fields.datetime: 'timestamp', - fields.binary: 'bytea', - fields.many2one: 'int4', - } - if field_type in type_dict: - f_type = (type_dict[field_type], type_dict[field_type]) + if field_type in FIELDS_TO_PGTYPES: + f_type = (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type]) elif isinstance(f, fields.float): if f.digits: f_type = ('numeric', 'NUMERIC') From 4b8be3a051cc20f2079e146f583919b77e662e88 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 17:39:35 +0200 Subject: [PATCH 049/304] [IMP] use early returns instead of a single return point bzr revid: xmo@openerp.com-20110909153935-uk4cggyv78nfdjqt --- openerp/osv/orm.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index f20b759f6f8..977c3c3deb4 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -509,28 +509,23 @@ def get_pg_type(f, type_override=None): field_type = type_override or type(f) if field_type in FIELDS_TO_PGTYPES: - f_type = (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type]) + return (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type]) elif isinstance(f, fields.float): if f.digits: - f_type = ('numeric', 'NUMERIC') - else: - f_type = ('float8', 'DOUBLE PRECISION') + return ('numeric', 'NUMERIC') + return ('float8', 'DOUBLE PRECISION') elif isinstance(f, (fields.char, fields.reference)): - f_type = ('varchar', pg_varchar(f.size)) + return ('varchar', pg_varchar(f.size)) elif isinstance(f, fields.selection): if isinstance(f.selection, list) and isinstance(f.selection[0][0], int): - f_type = ('int4', 'INTEGER') - else: - f_type = ('varchar', pg_varchar(getattr(f, 'size', None))) + return ('int4', 'INTEGER') + return ('varchar', pg_varchar(getattr(f, 'size', None))) elif isinstance(f, fields.function): if f._type = 'selection': - f_type = ('varchar', pg_varchar()) - else: - f_type = get_pg_type(f, getattr(fields, f._type)) - else: - logging.getLogger('orm').warn('%s type not supported!', field_type) - f_type = None - return f_type + return ('varchar', pg_varchar()) + return get_pg_type(f, getattr(fields, f._type)) + logging.getLogger('orm').warn('%s type not supported!', field_type) + return class MetaModel(type): From cbaa36c8ccf31d960d497834f72a2a79b0edb014 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 17:42:42 +0200 Subject: [PATCH 050/304] [FIX] typo, might want to launch the software from time to time while changing stuff bzr revid: xmo@openerp.com-20110909154242-gve3dgsbmg6crkya --- openerp/osv/orm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 977c3c3deb4..4c592a53342 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -521,7 +521,7 @@ def get_pg_type(f, type_override=None): return ('int4', 'INTEGER') return ('varchar', pg_varchar(getattr(f, 'size', None))) elif isinstance(f, fields.function): - if f._type = 'selection': + if f._type == 'selection': return ('varchar', pg_varchar()) return get_pg_type(f, getattr(fields, f._type)) logging.getLogger('orm').warn('%s type not supported!', field_type) @@ -2833,7 +2833,7 @@ class orm(orm_template): if f_obj_type: ok = False casts = [ - ('text', 'char', pg_varchar(f.size), '::%s' % pg_varchar(f.size), + ('text', 'char', pg_varchar(f.size), '::%s' % pg_varchar(f.size)), ('varchar', 'text', 'TEXT', ''), ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]), ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'), From 0d965c2fca76a73bc301da6769d34e1360a9e418 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 17:58:48 +0200 Subject: [PATCH 051/304] [FIX] if types can be overridden, type-checking against the overriden type instead of the non-overridden instance might be a good idea as well bzr revid: xmo@openerp.com-20110909155848-5no3pvtfugqmhm8q --- openerp/osv/orm.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 4c592a53342..92a22a29f9a 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -510,17 +510,17 @@ def get_pg_type(f, type_override=None): if field_type in FIELDS_TO_PGTYPES: return (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type]) - elif isinstance(f, fields.float): + elif issubclass(field_type, fields.float): if f.digits: return ('numeric', 'NUMERIC') return ('float8', 'DOUBLE PRECISION') - elif isinstance(f, (fields.char, fields.reference)): + elif issubclass(field_type, (fields.char, fields.reference)): return ('varchar', pg_varchar(f.size)) - elif isinstance(f, fields.selection): + elif issubclass(field_type, fields.selection): if isinstance(f.selection, list) and isinstance(f.selection[0][0], int): return ('int4', 'INTEGER') return ('varchar', pg_varchar(getattr(f, 'size', None))) - elif isinstance(f, fields.function): + elif issubclass(field_type, fields.function): if f._type == 'selection': return ('varchar', pg_varchar()) return get_pg_type(f, getattr(fields, f._type)) From 9c83238f18dd651e0df0bde54c050cfc4a08a694 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 18:16:23 +0200 Subject: [PATCH 052/304] [FIX] reimplement quality selection field API a size attribute of -1 means the selection field is an int4, even if the selection is a callable bzr revid: xmo@openerp.com-20110909161623-nab218ea48ucj4qt --- 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 92a22a29f9a..6f794469529 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -517,7 +517,8 @@ def get_pg_type(f, type_override=None): elif issubclass(field_type, (fields.char, fields.reference)): return ('varchar', pg_varchar(f.size)) elif issubclass(field_type, fields.selection): - if isinstance(f.selection, list) and isinstance(f.selection[0][0], int): + if (isinstance(f.selection, list) and isinstance(f.selection[0][0], int))\ + or getattr(f, 'size', None) == -1: return ('int4', 'INTEGER') return ('varchar', pg_varchar(getattr(f, 'size', None))) elif issubclass(field_type, fields.function): From e575211116c099e24ae294e0e5c08f617ae3a9fb Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 9 Sep 2011 18:29:29 +0200 Subject: [PATCH 053/304] [IMP] add a pair of assertions on the parameter to VARCHAR, just in case bzr revid: xmo@openerp.com-20110909162929-w1l328oh14ccn7f8 --- openerp/osv/orm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 6f794469529..9492e3d812b 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -485,6 +485,12 @@ def pg_varchar(size=0): :rtype: str """ if size: + if not isinstance(size, int): + raise TypeError("VARCHAR parameter should be an int, got %s" + % type(size)) + if size < 0: + raise ValueError("VARCHAR parameter can not be negative, got %d" + % size) return 'VARCHAR(%d)' % size return 'VARCHAR' From 7ce7230c4010cf5e7099bbd7c34b4cba19b54388 Mon Sep 17 00:00:00 2001 From: "Harry (OpenERP)" Date: Tue, 13 Sep 2011 18:24:56 +0530 Subject: [PATCH 054/304] [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 055/304] [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 056/304] [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 057/304] [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 058/304] [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 059/304] [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 060/304] [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 061/304] [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 062/304] [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 063/304] [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 064/304] [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 065/304] [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 066/304] [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 067/304] [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 068/304] [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 069/304] [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 070/304] [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 @@ + + + +