diff options
Diffstat (limited to 'addons/import_base/import_framework.py')
| -rw-r--r-- | addons/import_base/import_framework.py | 501 |
1 files changed, 0 insertions, 501 deletions
diff --git a/addons/import_base/import_framework.py b/addons/import_base/import_framework.py deleted file mode 100644 index 559f42c8974..00000000000 --- a/addons/import_base/import_framework.py +++ /dev/null @@ -1,501 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). -# -# 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 <http://www.gnu.org/licenses/>. -# -############################################################################## -import pprint -import mapper -import pooler -import tools -from tools.translate import _ - -from threading import Thread -import datetime -import logging -import StringIO -import traceback -pp = pprint.PrettyPrinter(indent=4) -_logger = logging.getLogger(__name__) - - - -class import_framework(Thread): - """ - This class should be extends, - get_data and get_mapping have to extends - get_state_map and initialize can be extended - for advanced purpose get_default_hook can also be extended - @see dummy import for a minimal exemple - """ - - """ - for import_object, this domain will avoid to find an already existing object - """ - DO_NOT_FIND_DOMAIN = [('id', '=', 0)] - - #TODO don't use context to pass credential parameters - def __init__(self, obj, cr, uid, instance_name, module_name, email_to_notify=False, context=None): - Thread.__init__(self) - self.external_id_field = 'id' - self.obj = obj - self.cr = cr - self.uid = uid - self.instance_name = instance_name - self.module_name = module_name - self.context = context or {} - self.email = email_to_notify - self.table_list = [] - self.initialize() - - """ - Abstract Method to be implemented in - the real instance - """ - def initialize(self): - """ - init before import - usually for the login - """ - pass - - def init_run(self): - """ - call after intialize run in the thread, not in the main process - TO use for long initialization operation - """ - pass - - def get_data(self, table): - """ - @return: a list of dictionaries - each dictionnaries contains the list of pair external_field_name : value - """ - return [{}] - - def get_link(self, from_table, ids, to_table): - """ - @return: a dictionaries that contains the association between the id (from_table) - and the list (to table) of id linked - """ - return {} - - def get_external_id(self, data): - """ - @return the external id - the default implementation return self.external_id_field (that has 'id') by default - if the name of id field is different, you can overwrite this method or change the value - of self.external_id_field - """ - return data[self.external_id_field] - - def get_mapping(self): - """ - @return: { TABLE_NAME : { - 'model' : 'openerp.model.name', - #if true import the table if not just resolve dependencies, use for meta package, by default => True - #Not required - 'import' : True or False, - #Not required - 'dependencies' : [TABLE_1, TABLE_2], - #Not required - 'hook' : self.function_name, #get the val dict of the object, return the same val dict or False - 'map' : { @see mapper - 'openerp_field_name' : 'external_field_name', or val('external_field_name') - 'openerp_field_id/id' : ref(TABLE_1, 'external_id_field'), #make the mapping between the external id and the xml on the right - 'openerp_field2_id/id_parent' : ref(TABLE_1,'external_id_field') #indicate a self dependencies on openerp_field2_id - 'state' : map_val('state_equivalent_field', mapping), # use get_state_map to make the mapping between the value of the field and the value of the state - 'text_field' : concat('field_1', 'field_2', .., delimiter=':'), #concat the value of the list of field in one - 'description_field' : ppconcat('field_1', 'field_2', .., delimiter='\n\t'), #same as above but with a prettier formatting - 'field' : call(callable, arg1, arg2, ..), #call the function with all the value, the function should send the value : self.callable - 'field' : callable - 'field' : call(method, val('external_field') interface of method is self, val where val is the value of the field - 'field' : const(value) #always set this field to value - + any custom mapper that you will define - } - }, - - } - """ - return {} - - def default_hook(self, val): - """ - this hook will be apply on each table that don't have hook - here we define the identity hook - """ - return val - - def _import_table(self, table): - data = self.get_data(table) - map = self.get_mapping()[table]['map'] - hook = self.get_mapping()[table].get('hook', self.default_hook) - model = self.get_mapping()[table]['model'] - - final_data = [] - for val in data: - res = hook(val) - if res: - final_data.append(res) - return self._save_data(model, dict(map), final_data, table) - - def _save_data(self, model, mapping, datas, table): - """ - @param model: the model of the object to import - @param table : the external table where the data come from - @param mapping : definition of the mapping - @see: get_mapping - @param datas : list of dictionnaries - datas = [data_1, data_2, ..] - data_i is a map external field_name => value - and each data_i have a external id => in data_id['id'] - """ - _logger.info(' Importing %s into %s' % (table, model)) - if not datas: - return (0, 'No data found') - mapping['id'] = 'id_new' - res = [] - - - self_dependencies = [] - for k in mapping.keys(): - if '_parent' in k: - self_dependencies.append((k[:-7], mapping.pop(k))) - - for data in datas: - for k, field_name in self_dependencies: - data[k] = data.get(field_name) and self._generate_xml_id(data.get(field_name), table) - - data['id_new'] = self._generate_xml_id(self.get_external_id(data), table) - fields, values = self._fields_mapp(data, mapping, table) - res.append(values) - - model_obj = self.obj.pool.get(model) - if not model_obj: - raise ValueError(_("%s is not a valid model name.") % model) - _logger.debug(_(" fields imported : ") + str(fields)) - (p, r, warning, s) = model_obj.import_data(self.cr, self.uid, fields, res, mode='update', current_module=self.module_name, noupdate=True, context=self.context) - for (field, field_name) in self_dependencies: - self._import_self_dependencies(model_obj, field, datas) - return (len(res), warning) - - def _import_self_dependencies(self, obj, parent_field, datas): - """ - @param parent_field: the name of the field that generate a self_dependencies, we call the object referenced in this - field the parent of the object - @param datas: a list of dictionnaries - Dictionnaries need to contains - id_new : the xml_id of the object - field_new : the xml_id of the parent - """ - fields = ['id', parent_field] - for data in datas: - if data.get(parent_field): - values = [data['id_new'], data[parent_field]] - obj.import_data(self.cr, self.uid, fields, [values], mode='update', current_module=self.module_name, noupdate=True, context=self.context) - - def _preprocess_mapping(self, mapping): - """ - Preprocess the mapping : - after the preprocces, everything is - callable in the val of the dictionary - - use to allow syntaxical sugar like 'field': 'external_field' - instead of 'field' : value('external_field') - """ - map = dict(mapping) - for key, value in map.items(): - if isinstance(value, basestring): - map[key] = mapper.value(value) - #set parent for instance of dbmapper - elif isinstance(value, mapper.dbmapper): - value.set_parent(self) - return map - - - def _fields_mapp(self,dict_sugar, openerp_dict, table): - """ - call all the mapper and transform data - to be compatible with import_data - """ - fields=[] - data_lst = [] - mapping = self._preprocess_mapping(openerp_dict) - for key,val in mapping.items(): - if key not in fields and dict_sugar: - fields.append(key) - value = val(dict(dict_sugar)) - data_lst.append(value) - return fields, data_lst - - def _generate_xml_id(self, name, table): - """ - @param name: name of the object, has to be unique in for a given table - @param table : table where the record we want generate come from - @return: a unique xml id for record, the xml_id will be the same given the same table and same name - To be used to avoid duplication of data that don't have ids - """ - sugar_instance = self.instance_name - name = name.replace('.', '_').replace(',', '_') - return sugar_instance + "_" + table + "_" + name - - - """ - Public interface of the framework - those function can be use in the callable function defined in the mapping - """ - def xml_id_exist(self, table, external_id): - """ - Check if the external id exist in the openerp database - in order to check if the id exist the table where it come from - should be provide - @return the xml_id generated if the external_id exist in the database or false - """ - if not external_id: - return False - - xml_id = self._generate_xml_id(external_id, table) - id = self.obj.pool.get('ir.model.data').search(self.cr, self.uid, [('name', '=', xml_id), ('module', '=', self.module_name)]) - return id and xml_id or False - - def name_exist(self, table, name, model): - """ - Check if the object with the name exist in the openerp database - in order to check if the id exist the table where it come from - should be provide and the model of the object - """ - fields = ['name'] - data = [name] - return self.import_object(fields, data, model, table, name, [('name', '=', name)]) - - def get_mapped_id(self, table, external_id, context=None): - """ - @return return the databse id linked with the external_id - """ - if not external_id: - return False - - xml_id = self._generate_xml_id(external_id, table) - return self.obj.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, self.module_name, xml_id)[1] - - def import_object_mapping(self, mapping, data, model, table, name, domain_search=False): - """ - same as import_objects but instead of two list fields and data, - this method take a dictionnaries : external_field : value - and the mapping similar to the one define in 'map' key - @see import_object, get_mapping - """ - fields, datas = self._fields_mapp(data, mapping, table) - return self.import_object(fields, datas, model, table, name, domain_search) - - def import_object(self, fields, data, model, table, name, domain_search=False): - """ - This method will import an object in the openerp, usefull for field that is only a char in sugar and is an object in openerp - use import_data that will take care to create/update or do nothing with the data - this method return the xml_id - - To be use, when you want to create an object or link if already exist - use DO_NOT_LINK_DOMAIN to create always a new object - @param fields: list of fields needed to create the object without id - @param data: the list of the data, in the same order as the field - ex : fields = ['firstname', 'lastname'] ; data = ['John', 'Mc donalds'] - @param model: the openerp's model of the create/update object - @param table: the table where data come from in sugarcrm, no need to fit the real name of openerp name, just need to be unique - @param unique_name: the name of the object that we want to create/update get the id - @param domain_search : the domain that should find the unique existing record - - @return: the xml_id of the ressources - """ - domain_search = not domain_search and [('name', 'ilike', name)] or domain_search - obj = self.obj.pool.get(model) - if not obj: #if the model doesn't exist - return False - - xml_id = self._generate_xml_id(name, table) - xml_ref = self.mapped_id_if_exist(model, domain_search, table, name) - fields.append('id') - data.append(xml_id) - obj.import_data(self.cr, self.uid, fields, [data], mode='update', current_module=self.module_name, noupdate=True, context=self.context) - return xml_ref or xml_id - - - def mapped_id_if_exist(self, model, domain, table, name): - """ - To be use when we want link with and existing object, if the object don't exist - just ignore. - @param domain : search domain to find existing record, should return a unique record - @param xml_id: xml_id give to the mapping - @param name: external_id or name of the object to create if there is no id - @param table: the name of the table of the object to map - @return : the xml_id if the record exist in the db, False otherwise - """ - obj = self.obj.pool.get(model) - ids = obj.search(self.cr, self.uid, domain, context=self.context) - if ids: - xml_id = self._generate_xml_id(name, table) - ir_model_data_obj = obj.pool.get('ir.model.data') - id = ir_model_data_obj._update(self.cr, self.uid, model, - self.module_name, {}, mode='update', xml_id=xml_id, - noupdate=True, res_id=ids[0], context=self.context) - return xml_id - return False - - - def set_table_list(self, table_list): - """ - Set the list of table to import, this method should be call before run - @param table_list: the list of external table to import - ['Leads', 'Opportunity'] - """ - self.table_list = table_list - - def run(self): - """ - Import all data into openerp, - this is the Entry point to launch the process of import - - - """ - self.data_started = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.cr = pooler.get_db(self.cr.dbname).cursor() - error = False - result = [] - try: - self.init_run() - imported = set() #to invoid importing 2 times the sames modules - for table in self.table_list: - to_import = self.get_mapping()[table].get('import', True) - if not table in imported: - res = self._resolve_dependencies(self.get_mapping()[table].get('dependencies', []), imported) - result.extend(res) - if to_import: - (position, warning) = self._import_table(table) - result.append((table, position, warning)) - imported.add(table) - self.cr.commit() - - except Exception, err: - sh = StringIO.StringIO() - traceback.print_exc(file=sh) - error = sh.getvalue() - print error - - - self.date_ended = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self._send_notification_email(result, error) - - self.cr.close() - - def _resolve_dependencies(self, dep, imported): - """ - import dependencies recursively - and avoid to import twice the same table - """ - result = [] - for dependency in dep: - if not dependency in imported: - to_import = self.get_mapping()[dependency].get('import', True) - res = self._resolve_dependencies(self.get_mapping()[dependency].get('dependencies', []), imported) - result.extend(res) - if to_import: - r = self._import_table(dependency) - (position, warning) = r - result.append((dependency, position, warning)) - imported.add(dependency) - return result - - def _send_notification_email(self, result, error): - if not self.email: - return False - email_obj = self.obj.pool.get('mail.mail') - email_id = email_obj.create(self.cr, self.uid, { - 'email_from' : 'import@module.openerp', - 'email_to' : self.email, - 'body_html' : self.get_email_body(result, error), - 'subject' : self.get_email_subject(result, error), - 'auto_delete' : True}) - email_obj.send(self.cr, self.uid, [email_id]) - if error: - _logger.error(_("Import failed due to an unexpected error")) - else: - _logger.info(_("Import finished, notification email sended")) - - def get_email_subject(self, result, error=False): - """ - This method define the subject of the email send by openerp at the end of import - @param result: a list of tuple - (table_name, number_of_record_created/updated, warning) for each table - @return the subject of the mail - - """ - if error: - return _("Data Import failed at %s due to an unexpected error") % self.date_ended - return _("Import of your data finished at %s") % self.date_ended - - def get_email_body(self, result, error=False): - """ - This method define the body of the email send by openerp at the end of import. The body is separated in two part - the header (@see get_body_header), and the generate body with the list of table and number of record imported. - If you want to keep this it's better to overwrite get_body_header - @param result: a list of tuple - (table_name, number_of_record_created/updated, warning) for each table - @return the subject of the mail - - """ - - body = _("started at %s and finished at %s \n") % (self.data_started, self.date_ended) - if error: - body += _("but failed, in consequence no data were imported to keep database consistency \n error : \n") + error - - for (table, nb, warning) in result: - if not warning: - warning = _("with no warning") - else: - warning = _("with warning : %s") % warning - body += _("%s has been successfully imported from %s %s, %s \n") % (nb, self.instance_name, table, warning) - return self.get_body_header(result) + "\n\n" + body - - def get_body_header(self, result): - """ - @return the first sentences written in the mail's body - """ - return _("The import of data \n instance name : %s \n") % self.instance_name - - - #TODO documentation test - def run_test(self): - back_get_data = self.get_data - back_get_link = self.get_link - back_init = self.initialize - self.get_data = self.get_data_test - self.get_link = self.get_link_test - self.initialize = self.intialize_test - self.run() - self.get_data = back_get_data - self.get_link = back_get_link - self.initialize = back_init - - def get_data_test(self, table): - return [{}] - - def get_link_test(self, from_table, ids, to_table): - return {} - - def intialize_test(self): - pass - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
