~openerp-commiter/openobject-addons/extra-6.0

« back to all changes in this revision

Viewing changes to base_external_referentials/external_osv.py

  • Committer: Albert Cervera i Areny
  • Date: 2011-06-14 09:51:35 UTC
  • mfrom: (5345.1.165 openobject-addons)
  • Revision ID: albert@nan-tic.com-20110614095135-1x3p6tmil5lxkl9b
Merge and add nan_remove_default_filters

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
import netsvc
28
28
 
29
29
class external_osv(osv.osv):
30
 
 
31
 
    def read_w_order(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
32
 
        res = self.read(cr, user, ids, fields_to_read, context, load)
33
 
        resultat = []
34
 
        for id in ids:
35
 
            resultat += [x for x in res if x['id'] == id]
36
 
        return resultat
37
 
 
38
 
    def browse_w_order(self, cr, uid, ids, context=None, list_class=None, fields_process={}):
39
 
        res = self.browse(cr, uid, ids, context, list_class, fields_process)
40
 
        resultat = []
41
 
        for id in ids:
42
 
            resultat += [x for x in res if x.id == id]
43
 
        return resultat
44
 
 
45
 
    def prefixed_id(self, id):
46
 
        """The reason why we don't just use the external id and put the model as the prefix is to avoid unique ir_model_data#name per module constraint violation."""
47
 
        return self._name.replace('.', '_') + '/' + str(id)
48
 
    
49
 
    def id_from_prefixed_id(self, prefixed_id):
50
 
        return prefixed_id.split(self._name.replace('.', '_') + '/')[1]
51
 
    
52
 
    def get_last_imported_external_id(self, cr, object_name, referential_id, where_clause):
53
 
        table_name = object_name.replace('.', '_')
54
 
        cr.execute("""
55
 
                   SELECT %(table_name)s.id, ir_model_data.name from %(table_name)s inner join ir_model_data
56
 
                   ON %(table_name)s.id = ir_model_data.res_id
57
 
                   WHERE ir_model_data.model=%%s %(where_clause)s
58
 
                     AND ir_model_data.external_referential_id = %%s
59
 
                   ORDER BY %(table_name)s.create_date DESC
60
 
                   LIMIT 1
61
 
                   """ % { 'table_name' : table_name, 'where_clause' : where_clause and ("and " + where_clause) or ""}
62
 
                   , (object_name, referential_id,))
63
 
        results = cr.fetchone()
64
 
        if results and len(results) > 0:
65
 
            return [results[0], results[1].split(object_name.replace('.', '_') +'/')[1]]
66
 
        else:
67
 
            return [False, False]
68
 
            
69
 
    def get_modified_ids(self, cr, uid, date=False, context=None): 
70
 
        """ This function will return the ids of the modified or created items of self object since the date
71
 
 
72
 
        @return: a table of this format : [[id1, last modified date], [id2, last modified date] ...] """
73
 
        if date:
74
 
            sql_request = "SELECT id, create_date, write_date FROM %s " % (self._name.replace('.', '_'),)
75
 
            sql_request += "WHERE create_date > %s OR write_date > %s;"
76
 
            cr.execute(sql_request, (date, date))
77
 
        else:
78
 
            sql_request = "SELECT id, create_date, write_date FROM %s " % (self._name.replace('.', '_'),)
79
 
            cr.execute(sql_request)
80
 
        l = cr.fetchall()
81
 
        res = []
82
 
        for p in l:
83
 
            if p[2]:
84
 
                res += [[p[0], p[2]]] 
85
 
            else:
86
 
                res += [[p[0], p[1]]] 
87
 
        return sorted(res, key=lambda date: date[1])
88
 
    
89
 
    def external_connection(self, cr, uid, DEBUG=False):
90
 
        """Should be overridden to provide valid external referential connection"""
91
 
        return False
92
 
    
93
 
    def oeid_to_extid(self, cr, uid, id, external_referential_id, context=None):
94
 
        """Returns the external id of a resource by its OpenERP id.
95
 
        Returns False if the resource id does not exists."""
96
 
        model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('res_id', '=', id), ('external_referential_id', '=', external_referential_id)])
97
 
        if model_data_ids and len(model_data_ids) > 0:
98
 
            prefixed_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids[0], ['name'])['name']
99
 
            ext_id = int(self.id_from_prefixed_id(prefixed_id))
100
 
            return ext_id
101
 
        return False
102
 
 
103
 
    def extid_to_existing_oeid(self, cr, uid, id, external_referential_id, context=None):
104
 
        """Returns the OpenERP id of a resource by its external id.
105
 
           Returns False if the resource does not exist."""
106
 
        if id:
107
 
            model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', self.prefixed_id(id)), ('model', '=', self._name), ('external_referential_id', '=', external_referential_id)])
108
 
            if model_data_ids:
109
 
                claimed_oe_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids[0], ['res_id'])['res_id']
110
 
 
111
 
                #because OpenERP might keep ir_model_data (is it a bug?) for deleted records, we check if record exists:
112
 
                ids = self.search(cr, uid, [('id', '=', claimed_oe_id)])
113
 
                if ids:
114
 
                    return ids[0]
115
 
        return False
116
 
 
117
 
    def extid_to_oeid(self, cr, uid, id, external_referential_id, context=None):
118
 
        """Returns the OpenERP ID of a resource by its external id.
119
 
        Creates the resource from the external connection if the resource does not exist."""
120
 
        #First get the external key field name
121
 
        #conversion external id -> OpenERP object using Object mapping_column_name key!
122
 
        if id:
123
 
            existing_id = self.extid_to_existing_oeid(cr, uid, id, external_referential_id, context)
124
 
            if existing_id:
125
 
                return existing_id
126
 
            try:
127
 
                if context and context.get('alternative_key', False): #FIXME dirty fix for Magento product.info id/sku mix bug: https://bugs.launchpad.net/magentoerpconnect/+bug/688225
128
 
                    id = context.get('alternative_key', False)
129
 
                result = self.get_external_data(cr, uid, self.external_connection(cr, uid, self.pool.get('external.referential').browse(cr, uid, external_referential_id)), external_referential_id, {}, {'id':id})
130
 
                if len(result['create_ids']) == 1:
131
 
                    return result['create_ids'][0]
132
 
            except Exception, error: #external system might return error because no such record exists
133
 
                print error
134
 
        return False
135
 
    
136
 
    def oevals_from_extdata(self, cr, uid, external_referential_id, data_record, key_field, mapping_lines, defaults, context):
137
 
        if context is None:
138
 
            context = {}
139
 
        vals = {} #Dictionary for create record
140
 
        for each_mapping_line in mapping_lines:
141
 
            #Type cast if the expression exists
142
 
            if each_mapping_line['external_field'] in data_record.keys():
143
 
                try:
144
 
                    if each_mapping_line['external_type'] and type(data_record.get(each_mapping_line['external_field'], False)) != unicode:
145
 
                        type_casted_field = eval(each_mapping_line['external_type'])(data_record.get(each_mapping_line['external_field'], False))
 
30
    pass #FIXME remove! only here for compatibility purpose for now
 
31
 
 
32
def read_w_order(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
 
33
    res = self.read(cr, user, ids, fields_to_read, context, load)
 
34
    resultat = []
 
35
    for id in ids:
 
36
        resultat += [x for x in res if x['id'] == id]
 
37
    return resultat
 
38
 
 
39
def browse_w_order(self, cr, uid, ids, context=None, list_class=None, fields_process={}):
 
40
    res = self.browse(cr, uid, ids, context, list_class, fields_process)
 
41
    resultat = []
 
42
    for id in ids:
 
43
        resultat += [x for x in res if x.id == id]
 
44
    return resultat
 
45
 
 
46
def prefixed_id(self, id):
 
47
    """The reason why we don't just use the external id and put the model as the prefix is to avoid unique ir_model_data#name per module constraint violation."""
 
48
    return self._name.replace('.', '_') + '/' + str(id)
 
49
 
 
50
def id_from_prefixed_id(self, prefixed_id):
 
51
    return prefixed_id.split(self._name.replace('.', '_') + '/')[1]
 
52
 
 
53
def get_last_imported_external_id(self, cr, object_name, referential_id, where_clause):
 
54
    table_name = object_name.replace('.', '_')
 
55
    cr.execute("""
 
56
               SELECT %(table_name)s.id, ir_model_data.name from %(table_name)s inner join ir_model_data
 
57
               ON %(table_name)s.id = ir_model_data.res_id
 
58
               WHERE ir_model_data.model=%%s %(where_clause)s
 
59
                 AND ir_model_data.external_referential_id = %%s
 
60
               ORDER BY %(table_name)s.create_date DESC
 
61
               LIMIT 1
 
62
               """ % { 'table_name' : table_name, 'where_clause' : where_clause and ("and " + where_clause) or ""}
 
63
               , (object_name, referential_id,))
 
64
    results = cr.fetchone()
 
65
    if results and len(results) > 0:
 
66
        return [results[0], results[1].split(object_name.replace('.', '_') +'/')[1]]
 
67
    else:
 
68
        return [False, False]
 
69
 
 
70
def get_modified_ids(self, cr, uid, date=False, context=None): 
 
71
    """ This function will return the ids of the modified or created items of self object since the date
 
72
 
 
73
    @return: a table of this format : [[id1, last modified date], [id2, last modified date] ...] """
 
74
    if date:
 
75
        sql_request = "SELECT id, create_date, write_date FROM %s " % (self._name.replace('.', '_'),)
 
76
        sql_request += "WHERE create_date > %s OR write_date > %s;"
 
77
        cr.execute(sql_request, (date, date))
 
78
    else:
 
79
        sql_request = "SELECT id, create_date, write_date FROM %s " % (self._name.replace('.', '_'),)
 
80
        cr.execute(sql_request)
 
81
    l = cr.fetchall()
 
82
    res = []
 
83
    for p in l:
 
84
        if p[2]:
 
85
            res += [[p[0], p[2]]]
 
86
        else:
 
87
            res += [[p[0], p[1]]]
 
88
    return sorted(res, key=lambda date: date[1])
 
89
 
 
90
def external_connection(self, cr, uid, DEBUG=False):
 
91
    """Should be overridden to provide valid external referential connection"""
 
92
    return False
 
93
 
 
94
def oeid_to_extid(self, cr, uid, id, external_referential_id, context=None):
 
95
    """Returns the external id of a resource by its OpenERP id.
 
96
    Returns False if the resource id does not exists."""
 
97
    model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('res_id', '=', id), ('external_referential_id', '=', external_referential_id)])
 
98
    if model_data_ids and len(model_data_ids) > 0:
 
99
        prefixed_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids[0], ['name'])['name']
 
100
        ext_id = int(self.id_from_prefixed_id(prefixed_id))
 
101
        return ext_id
 
102
    return False
 
103
 
 
104
def extid_to_existing_oeid(self, cr, uid, id, external_referential_id, context=None):
 
105
    """Returns the OpenERP id of a resource by its external id.
 
106
       Returns False if the resource does not exist."""
 
107
    if id:
 
108
        model_data_ids = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', self.prefixed_id(id)), ('model', '=', self._name), ('external_referential_id', '=', external_referential_id)])
 
109
        if model_data_ids:
 
110
            claimed_oe_id = self.pool.get('ir.model.data').read(cr, uid, model_data_ids[0], ['res_id'])['res_id']
 
111
 
 
112
            #because OpenERP might keep ir_model_data (is it a bug?) for deleted records, we check if record exists:
 
113
            ids = self.search(cr, uid, [('id', '=', claimed_oe_id)])
 
114
            if ids:
 
115
                return ids[0]
 
116
    return False
 
117
 
 
118
def extid_to_oeid(self, cr, uid, id, external_referential_id, context=None):
 
119
    """Returns the OpenERP ID of a resource by its external id.
 
120
    Creates the resource from the external connection if the resource does not exist."""
 
121
    #First get the external key field name
 
122
    #conversion external id -> OpenERP object using Object mapping_column_name key!
 
123
    if id:
 
124
        existing_id = self.extid_to_existing_oeid(cr, uid, id, external_referential_id, context)
 
125
        if existing_id:
 
126
            return existing_id
 
127
        try:
 
128
            if context and context.get('alternative_key', False): #FIXME dirty fix for Magento product.info id/sku mix bug: https://bugs.launchpad.net/magentoerpconnect/+bug/688225
 
129
                id = context.get('alternative_key', False)
 
130
            result = self.get_external_data(cr, uid, self.external_connection(cr, uid, self.pool.get('external.referential').browse(cr, uid, external_referential_id)), external_referential_id, {}, {'id':id})
 
131
            if len(result['create_ids']) == 1:
 
132
                return result['create_ids'][0]
 
133
        except Exception, error: #external system might return error because no such record exists
 
134
            print error
 
135
    return False
 
136
 
 
137
def oevals_from_extdata(self, cr, uid, external_referential_id, data_record, key_field, mapping_lines, defaults, context):
 
138
    if context is None:
 
139
        context = {}
 
140
    vals = {} #Dictionary for create record
 
141
    for each_mapping_line in mapping_lines:
 
142
        #Type cast if the expression exists
 
143
        if each_mapping_line['external_field'] in data_record.keys():
 
144
            try:
 
145
                if each_mapping_line['external_type'] and type(data_record.get(each_mapping_line['external_field'], False)) != unicode:
 
146
                    type_casted_field = eval(each_mapping_line['external_type'])(data_record.get(each_mapping_line['external_field'], False))
 
147
                else:
 
148
                    type_casted_field = data_record.get(each_mapping_line['external_field'], False)
 
149
                if type_casted_field in ['None', 'False']:
 
150
                    type_casted_field = False
 
151
            except Exception, e:
 
152
                type_casted_field = False
 
153
            #Build the space for expr
 
154
            space = {
 
155
                        'self':self,
 
156
                        'cr':cr,
 
157
                        'uid':uid,
 
158
                        'data':data_record,
 
159
                        'external_referential_id':external_referential_id,
 
160
                        'defaults':defaults,
 
161
                        'context':context,
 
162
                        'ifield':type_casted_field,
 
163
                        'conn':context.get('conn_obj', False),
 
164
                        'base64':base64,
 
165
                        'vals':vals
 
166
                    }
 
167
            #The expression should return value in list of tuple format
 
168
            #eg[('name','Sharoon'),('age',20)] -> vals = {'name':'Sharoon', 'age':20}
 
169
            try:
 
170
                exec each_mapping_line['in_function'] in space
 
171
            except Exception, e:
 
172
                logger = netsvc.Logger()
 
173
                logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Error in import mapping: %r" % (each_mapping_line['in_function'],))
 
174
                del(space['__builtins__'])
 
175
                logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Mapping Context: %r" % (space,))
 
176
                logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Exception: %r" % (e,))
 
177
            result = space.get('result', False)
 
178
            #If result exists and is of type list
 
179
            if result and type(result) == list:
 
180
                for each_tuple in result:
 
181
                    if type(each_tuple) == tuple and len(each_tuple) == 2:
 
182
                        vals[each_tuple[0]] = each_tuple[1]
 
183
    #Every mapping line has now been translated into vals dictionary, now set defaults if any
 
184
    for each_default_entry in defaults.keys():
 
185
        vals[each_default_entry] = defaults[each_default_entry]
 
186
 
 
187
    return vals
 
188
 
 
189
 
 
190
def get_external_data(self, cr, uid, conn, external_referential_id, defaults=None, context=None):
 
191
    """Constructs data using WS or other synch protocols and then call ext_import on it"""
 
192
    return {'create_ids': [], 'write_ids': []}
 
193
 
 
194
def add_external_reference(self, cr, uid, id, external_id, external_referential_id, context=None):
 
195
    '''Add an external id on an existing object'''
 
196
    if type(id) == list:
 
197
        id=id[0]
 
198
    ir_model_data_vals = {
 
199
        'name': self.prefixed_id(external_id),
 
200
        'model': self._name,
 
201
        'res_id': id,
 
202
        'external_referential_id': external_referential_id,
 
203
        'module': 'extref/' + self.pool.get('external.referential').read(cr, uid, external_referential_id, ['name'])['name']
 
204
        }
 
205
    self.pool.get('ir.model.data').create(cr, uid, ir_model_data_vals, context=context)
 
206
    return True
 
207
 
 
208
def ext_import(self, cr, uid, data, external_referential_id, defaults=None, context=None):
 
209
    if defaults is None:
 
210
        defaults = {}
 
211
    if context is None:
 
212
        context = {}
 
213
 
 
214
    #Inward data has to be list of dictionary
 
215
    #This function will import a given set of data as list of dictionary into Open ERP
 
216
    write_ids = []  #Will record ids of records modified, not sure if will be used
 
217
    create_ids = [] #Will record ids of newly created records, not sure if will be used
 
218
    logger = netsvc.Logger()
 
219
    if data:
 
220
        mapping_id = self.pool.get('external.mapping').search(cr, uid, [('model', '=', self._name), ('referential_id', '=', external_referential_id)])
 
221
        if mapping_id:
 
222
            #If a mapping exists for current model, search for mapping lines
 
223
            mapping_line_ids = self.pool.get('external.mapping.line').search(cr, uid, [('mapping_id', '=', mapping_id), ('type', 'in', ['in_out', 'in'])])
 
224
            mapping_lines = self.pool.get('external.mapping.line').read(cr, uid, mapping_line_ids, ['external_field', 'external_type', 'in_function'])
 
225
            if mapping_lines:
 
226
                #if mapping lines exist find the data conversion for each row in inward data
 
227
                for_key_field = self.pool.get('external.mapping').read(cr, uid, mapping_id[0], ['external_key_name'])['external_key_name']
 
228
                for each_row in data:
 
229
                    vals = self.oevals_from_extdata(cr, uid, external_referential_id, each_row, for_key_field, mapping_lines, defaults, context)
 
230
                    #perform a record check, for that we need foreign field
 
231
                    external_id = vals.get(for_key_field, False) or each_row.get(for_key_field, False) or each_row.get('external_id', False)
 
232
                    #del vals[for_key_field] looks like it is affecting the import :(
 
233
                    #Check if record exists
 
234
                    existing_ir_model_data_id = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('name', '=', self.prefixed_id(external_id)), ('external_referential_id', '=', external_referential_id)])
 
235
                    record_test_id = False
 
236
                    if existing_ir_model_data_id:
 
237
                        existing_rec_id = self.pool.get('ir.model.data').read(cr, uid, existing_ir_model_data_id, ['res_id'])[0]['res_id']
 
238
 
 
239
                        #Note: OpenERP cleans up ir_model_data which res_id records have been deleted only at server update because that would be a perf penalty,
 
240
                        #so we take care of it here:
 
241
                        record_test_id = self.search(cr, uid, [('id', '=', existing_rec_id)])
 
242
                        if not record_test_id:
 
243
                            self.pool.get('ir.model.data').unlink(cr, uid, existing_ir_model_data_id)
 
244
 
 
245
                    if record_test_id:
 
246
                        if vals.get(for_key_field, False):
 
247
                            del vals[for_key_field]
 
248
                        if self.oe_update(cr, uid, existing_rec_id, vals, each_row, external_referential_id, defaults, context):
 
249
                            write_ids.append(existing_rec_id)
 
250
                            self.pool.get('ir.model.data').write(cr, uid, existing_ir_model_data_id, {'res_id':existing_rec_id})
 
251
                            logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Updated in OpenERP %s from External Ref with external_id %s and OpenERP id %s successfully" %(self._name, external_id, existing_rec_id))
 
252
 
146
253
                    else:
147
 
                        type_casted_field = data_record.get(each_mapping_line['external_field'], False)
148
 
                    if type_casted_field in ['None', 'False']:
149
 
                        type_casted_field = False
150
 
                except Exception, e:
151
 
                    type_casted_field = False
152
 
                #Build the space for expr
153
 
                space = {
154
 
                            'self':self,
155
 
                            'cr':cr,
156
 
                            'uid':uid,
157
 
                            'data':data_record,
158
 
                            'external_referential_id':external_referential_id,
159
 
                            'defaults':defaults,
160
 
                            'context':context,
161
 
                            'ifield':type_casted_field,
162
 
                            'conn':context.get('conn_obj', False),
163
 
                            'base64':base64,
164
 
                            'vals':vals
 
254
                        crid = self.oe_create(cr, uid, vals, each_row, external_referential_id, defaults, context)
 
255
                        create_ids.append(crid)
 
256
                        ir_model_data_vals = {
 
257
                            'name': self.prefixed_id(external_id),
 
258
                            'model': self._name,
 
259
                            'res_id': crid,
 
260
                            'external_referential_id': external_referential_id,
 
261
                            'module': 'extref/' + self.pool.get('external.referential').read(cr, uid, external_referential_id, ['name'])['name']
165
262
                        }
166
 
                #The expression should return value in list of tuple format
167
 
                #eg[('name','Sharoon'),('age',20)] -> vals = {'name':'Sharoon', 'age':20}
168
 
                try:
169
 
                    exec each_mapping_line['in_function'] in space
170
 
                except Exception, e:
171
 
                    logger = netsvc.Logger()
172
 
                    logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Error in import mapping: %r" % (each_mapping_line['in_function'],))
173
 
                    del(space['__builtins__'])
174
 
                    logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Mapping Context: %r" % (space,))
175
 
                    logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Exception: %r" % (e,))
176
 
                result = space.get('result', False)
177
 
                #If result exists and is of type list
178
 
                if result and type(result) == list:
179
 
                    for each_tuple in result:
180
 
                        if type(each_tuple) == tuple and len(each_tuple) == 2:
181
 
                            vals[each_tuple[0]] = each_tuple[1] 
182
 
        #Every mapping line has now been translated into vals dictionary, now set defaults if any
183
 
        for each_default_entry in defaults.keys():
184
 
            vals[each_default_entry] = defaults[each_default_entry]
185
 
 
186
 
        return vals
187
 
 
188
 
        
189
 
    def get_external_data(self, cr, uid, conn, external_referential_id, defaults=None, context=None):
190
 
        """Constructs data using WS or other synch protocols and then call ext_import on it"""
191
 
        return {'create_ids': [], 'write_ids': []}
192
 
 
193
 
    def ext_import(self, cr, uid, data, external_referential_id, defaults=None, context=None):
194
 
        if defaults is None:
195
 
            defaults = {}
196
 
        if context is None:
197
 
            context = {}
198
 
 
199
 
        #Inward data has to be list of dictionary
200
 
        #This function will import a given set of data as list of dictionary into Open ERP
201
 
        write_ids = []  #Will record ids of records modified, not sure if will be used
202
 
        create_ids = [] #Will record ids of newly created records, not sure if will be used
203
 
        logger = netsvc.Logger()
204
 
        if data:
205
 
            mapping_id = self.pool.get('external.mapping').search(cr, uid, [('model', '=', self._name), ('referential_id', '=', external_referential_id)])
 
263
                        self.pool.get('ir.model.data').create(cr, uid, ir_model_data_vals)
 
264
                        logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Created in OpenERP %s from External Ref with external_id %s and OpenERP id %s successfully" %(self._name, external_id, crid))
 
265
                    cr.commit()
 
266
 
 
267
    return {'create_ids': create_ids, 'write_ids': write_ids}
 
268
 
 
269
 
 
270
def retry_import(self, cr, uid, id, external_referential_id, defaults=None, context=None):
 
271
    """ When we import again a previously failed import
 
272
    """
 
273
    raise osv.except_osv(_("Not Implemented"), _("Not Implemented in abstract base module!"))
 
274
 
 
275
def oe_update(self, cr, uid, existing_rec_id, vals, data, external_referential_id, defaults, context):
 
276
    return self.write(cr, uid, existing_rec_id, vals, context)
 
277
 
 
278
 
 
279
def oe_create(self, cr, uid, vals, data, external_referential_id, defaults, context):
 
280
    return self.create(cr, uid, vals, context)
 
281
 
 
282
 
 
283
def extdata_from_oevals(self, cr, uid, external_referential_id, data_record, mapping_lines, defaults, context=None):
 
284
    if context is None:
 
285
        context = {}
 
286
    vals = {} #Dictionary for record
 
287
    for each_mapping_line in mapping_lines:
 
288
        #Build the space for expr
 
289
        space = {
 
290
            'self':self,
 
291
            'cr':cr,
 
292
            'uid':uid,
 
293
            'external_referential_id':external_referential_id,
 
294
            'defaults':defaults,
 
295
            'context':context,
 
296
            'record':data_record,
 
297
            'conn':context.get('conn_obj', False),
 
298
            'base64':base64
 
299
        }
 
300
        #The expression should return value in list of tuple format
 
301
        #eg[('name','Sharoon'),('age',20)] -> vals = {'name':'Sharoon', 'age':20}
 
302
        if each_mapping_line['out_function']:
 
303
            try:
 
304
                exec each_mapping_line['out_function'] in space
 
305
            except Exception, e:
 
306
                logger = netsvc.Logger()
 
307
                logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Error in import mapping: %r" % (each_mapping_line['out_function'],))
 
308
                del(space['__builtins__'])
 
309
                logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Mapping Context: %r" % (space,))
 
310
                logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Exception: %r" % (e,))
 
311
 
 
312
            result = space.get('result', False)
 
313
            #If result exists and is of type list
 
314
            if result and type(result) == list:
 
315
                for each_tuple in result:
 
316
                    if type(each_tuple) == tuple and len(each_tuple) == 2:
 
317
                        vals[each_tuple[0]] = each_tuple[1]
 
318
    #Every mapping line has now been translated into vals dictionary, now set defaults if any
 
319
    for each_default_entry in defaults.keys():
 
320
        vals[each_default_entry] = defaults[each_default_entry]
 
321
 
 
322
    return vals
 
323
 
 
324
 
 
325
def ext_export(self, cr, uid, ids, external_referential_ids=[], defaults={}, context=None):
 
326
    if context is None:
 
327
        context = {}
 
328
    #external_referential_ids has to be a list
 
329
    logger = netsvc.Logger()
 
330
    report_line_obj = self.pool.get('external.report.line')
 
331
    write_ids = []  #Will record ids of records modified, not sure if will be used
 
332
    create_ids = [] #Will record ids of newly created records, not sure if will be used
 
333
    for record_data in self.read_w_order(cr, uid, ids, [], context):
 
334
        #If no external_ref_ids are mentioned, then take all ext_ref_this item has
 
335
        if not external_referential_ids:
 
336
            ir_model_data_recids = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('res_id', '=', id), ('module', 'ilike', 'extref')])
 
337
            if ir_model_data_recids:
 
338
                for each_model_rec in self.pool.get('ir.model.data').read(cr, uid, ir_model_data_recids, ['external_referential_id']):
 
339
                    if each_model_rec['external_referential_id']:
 
340
                        external_referential_ids.append(each_model_rec['external_referential_id'][0])
 
341
        #if still there no external_referential_ids then export to all referentials
 
342
        if not external_referential_ids:
 
343
            external_referential_ids = self.pool.get('external.referential').search(cr, uid, [])
 
344
        #Do an export for each external ID
 
345
        for ext_ref_id in external_referential_ids:
 
346
            #Find the mapping record now
 
347
            mapping_id = self.pool.get('external.mapping').search(cr, uid, [('model', '=', self._name), ('referential_id', '=', ext_ref_id)])
206
348
            if mapping_id:
207
349
                #If a mapping exists for current model, search for mapping lines
208
 
                mapping_line_ids = self.pool.get('external.mapping.line').search(cr, uid, [('mapping_id', '=', mapping_id), ('type', 'in', ['in_out', 'in'])])
209
 
                mapping_lines = self.pool.get('external.mapping.line').read(cr, uid, mapping_line_ids, ['external_field', 'external_type', 'in_function'])
 
350
                mapping_line_ids = self.pool.get('external.mapping.line').search(cr, uid, [('mapping_id', '=', mapping_id), ('type', 'in', ['in_out', 'out'])])
 
351
                mapping_lines = self.pool.get('external.mapping.line').read(cr, uid, mapping_line_ids, ['external_field', 'out_function'])
210
352
                if mapping_lines:
211
353
                    #if mapping lines exist find the data conversion for each row in inward data
212
 
                    for_key_field = self.pool.get('external.mapping').read(cr, uid, mapping_id[0], ['external_key_name'])['external_key_name']
213
 
                    for each_row in data:
214
 
                        vals = self.oevals_from_extdata(cr, uid, external_referential_id, each_row, for_key_field, mapping_lines, defaults, context)
215
 
                        #perform a record check, for that we need foreign field
216
 
                        external_id = vals.get(for_key_field, False) or each_row.get(for_key_field, False) or each_row.get('external_id', False)
217
 
                        #del vals[for_key_field] looks like it is affecting the import :(
218
 
                        #Check if record exists
219
 
                        existing_ir_model_data_id = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('name', '=', self.prefixed_id(external_id)), ('external_referential_id', '=', external_referential_id)])
220
 
                        record_test_id = False
221
 
                        if existing_ir_model_data_id:
222
 
                            existing_rec_id = self.pool.get('ir.model.data').read(cr, uid, existing_ir_model_data_id, ['res_id'])[0]['res_id']
223
 
 
224
 
                            #Note: OpenERP cleans up ir_model_data which res_id records have been deleted only at server update because that would be a perf penalty,
225
 
                            #so we take care of it here:
226
 
                            record_test_id = self.search(cr, uid, [('id', '=', existing_rec_id)])
227
 
                            if not record_test_id:
228
 
                                self.pool.get('ir.model.data').unlink(cr, uid, existing_ir_model_data_id)
229
 
 
230
 
                        if record_test_id:
231
 
                            if vals.get(for_key_field, False):
232
 
                                del vals[for_key_field]
233
 
                            if self.oe_update(cr, uid, existing_rec_id, vals, each_row, external_referential_id, defaults, context):
234
 
                                write_ids.append(existing_rec_id)
235
 
                                self.pool.get('ir.model.data').write(cr, uid, existing_ir_model_data_id, {'res_id':existing_rec_id})
236
 
                                logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Updated in OpenERP %s from External Ref with external_id %s and OpenERP id %s successfully" %(self._name, external_id, existing_rec_id))
237
 
 
238
 
                        else:
239
 
                            crid = self.oe_create(cr, uid, vals, each_row, external_referential_id, defaults, context)
240
 
                            create_ids.append(crid)
241
 
                            ir_model_data_vals = {
242
 
                                'name': self.prefixed_id(external_id),
243
 
                                'model': self._name,
244
 
                                'res_id': crid,
245
 
                                'external_referential_id': external_referential_id,
246
 
                                'module': 'extref/' + self.pool.get('external.referential').read(cr, uid, external_referential_id, ['name'])['name']
247
 
                            }
248
 
                            self.pool.get('ir.model.data').create(cr, uid, ir_model_data_vals)
249
 
                            logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Created in OpenERP %s from External Ref with external_id %s and OpenERP id %s successfully" %(self._name, external_id, crid))
250
 
                        cr.commit()
251
 
 
252
 
        return {'create_ids': create_ids, 'write_ids': write_ids}
253
 
 
254
 
    def oe_update(self, cr, uid, existing_rec_id, vals, data, external_referential_id, defaults, context):
255
 
        return self.write(cr, uid, existing_rec_id, vals, context)
256
 
 
257
 
    
258
 
    def oe_create(self, cr, uid, vals, data, external_referential_id, defaults, context):
259
 
        return self.create(cr, uid, vals, context)
260
 
    
261
 
 
262
 
    def extdata_from_oevals(self, cr, uid, external_referential_id, data_record, mapping_lines, defaults, context=None):
263
 
        if context is None:
264
 
            context = {}
265
 
        vals = {} #Dictionary for record
266
 
        for each_mapping_line in mapping_lines:
267
 
            #Build the space for expr
268
 
            space = {
269
 
                'self':self,
270
 
                'cr':cr,
271
 
                'uid':uid,
272
 
                'external_referential_id':external_referential_id,
273
 
                'defaults':defaults,
274
 
                'context':context,
275
 
                'record':data_record,
276
 
                'conn':context.get('conn_obj', False),
277
 
                'base64':base64
278
 
            }
279
 
            #The expression should return value in list of tuple format
280
 
            #eg[('name','Sharoon'),('age',20)] -> vals = {'name':'Sharoon', 'age':20}
281
 
            if each_mapping_line['out_function']:
282
 
                try:
283
 
                    exec each_mapping_line['out_function'] in space
284
 
                except Exception, e:
285
 
                    logger = netsvc.Logger()
286
 
                    logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Error in import mapping: %r" % (each_mapping_line['out_function'],))
287
 
                    del(space['__builtins__'])
288
 
                    logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Mapping Context: %r" % (space,))
289
 
                    logger.notifyChannel('extdata_from_oevals', netsvc.LOG_DEBUG, "Exception: %r" % (e,))
290
 
 
291
 
                result = space.get('result', False)
292
 
                #If result exists and is of type list
293
 
                if result and type(result) == list:
294
 
                    for each_tuple in result:
295
 
                        if type(each_tuple) == tuple and len(each_tuple) == 2:
296
 
                            vals[each_tuple[0]] = each_tuple[1]
297
 
        #Every mapping line has now been translated into vals dictionary, now set defaults if any
298
 
        for each_default_entry in defaults.keys():
299
 
            vals[each_default_entry] = defaults[each_default_entry]
300
 
            
301
 
        return vals
302
 
 
303
 
    
304
 
    def ext_export(self, cr, uid, ids, external_referential_ids=[], defaults={}, context=None):
305
 
        if context is None:
306
 
            context = {}
307
 
        #external_referential_ids has to be a list
308
 
        logger = netsvc.Logger()
309
 
        write_ids = []  #Will record ids of records modified, not sure if will be used
310
 
        create_ids = [] #Will record ids of newly created records, not sure if will be used
311
 
        for record_data in self.read_w_order(cr, uid, ids, [], context):
312
 
            #If no external_ref_ids are mentioned, then take all ext_ref_this item has
313
 
            if not external_referential_ids:
314
 
                ir_model_data_recids = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('res_id', '=', id), ('module', 'ilike', 'extref')])
315
 
                if ir_model_data_recids:
316
 
                    for each_model_rec in self.pool.get('ir.model.data').read(cr, uid, ir_model_data_recids, ['external_referential_id']):
317
 
                        if each_model_rec['external_referential_id']:
318
 
                            external_referential_ids.append(each_model_rec['external_referential_id'][0])
319
 
            #if still there no external_referential_ids then export to all referentials
320
 
            if not external_referential_ids:
321
 
                external_referential_ids = self.pool.get('external.referential').search(cr, uid, [])
322
 
            #Do an export for each external ID
323
 
            for ext_ref_id in external_referential_ids:
324
 
                #Find the mapping record now
325
 
                mapping_id = self.pool.get('external.mapping').search(cr, uid, [('model', '=', self._name), ('referential_id', '=', ext_ref_id)])
326
 
                if mapping_id:
327
 
                    #If a mapping exists for current model, search for mapping lines
328
 
                    mapping_line_ids = self.pool.get('external.mapping.line').search(cr, uid, [('mapping_id', '=', mapping_id), ('type', 'in', ['in_out', 'out'])])
329
 
                    mapping_lines = self.pool.get('external.mapping.line').read(cr, uid, mapping_line_ids, ['external_field', 'out_function'])
330
 
                    if mapping_lines:
331
 
                        #if mapping lines exist find the data conversion for each row in inward data
332
 
                        exp_data = self.extdata_from_oevals(cr, uid, ext_ref_id, record_data, mapping_lines, defaults, context)
333
 
                        #Check if export for this referential demands a create or update
334
 
                        rec_check_ids = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('res_id', '=', record_data['id']), ('module', 'ilike', 'extref'), ('external_referential_id', '=', ext_ref_id)])
335
 
                        #rec_check_ids will indicate if the product already has a mapping record with ext system
336
 
                        mapping_id = self.pool.get('external.mapping').search(cr, uid, [('model', '=', self._name), ('referential_id', '=', ext_ref_id)])
337
 
                        if mapping_id and len(mapping_id) == 1:
338
 
                            mapping_rec = self.pool.get('external.mapping').read(cr, uid, mapping_id[0], ['external_update_method', 'external_create_method'])
339
 
                            conn = context.get('conn_obj', False)
340
 
                            if rec_check_ids and mapping_rec and len(rec_check_ids) == 1:
341
 
                                ext_id = self.oeid_to_extid(cr, uid, record_data['id'], ext_ref_id, context)
342
 
 
343
 
                                if not context.get('force', False):#TODO rename this context's key in 'no_date_check' or something like that
344
 
                                    #Record exists, check if update is required, for that collect last update times from ir.data & record
345
 
                                    last_exported_times = self.pool.get('ir.model.data').read(cr, uid, rec_check_ids[0], ['write_date', 'create_date'])
346
 
                                    last_exported_time = last_exported_times.get('write_date', False) or last_exported_times.get('create_date', False)
347
 
                                    this_record_times = self.read(cr, uid, record_data['id'], ['write_date', 'create_date'])
348
 
                                    last_updated_time = this_record_times.get('write_date', False) or this_record_times.get('create_date', False)
349
 
    
350
 
                                    if not last_updated_time: #strangely seems that on inherits structure, write_date/create_date are False for children
351
 
                                        cr.execute("select write_date, create_date from %s where id=%s;" % (self._name.replace('.', '_'), record_data['id']))
352
 
                                        read = cr.fetchone()
353
 
                                        last_updated_time = read[0] and read[0].split('.')[0] or read[1] and read[1].split('.')[0] or False
354
 
                                        
355
 
                                    if last_updated_time and last_exported_time:
356
 
                                        last_exported_time = datetime.datetime.fromtimestamp(time.mktime(time.strptime(last_exported_time, '%Y-%m-%d %H:%M:%S')))
357
 
                                        last_updated_time = datetime.datetime.fromtimestamp(time.mktime(time.strptime(last_updated_time, '%Y-%m-%d %H:%M:%S')))
358
 
                                        if last_exported_time + datetime.timedelta(seconds=1) > last_updated_time:
359
 
                                            continue
360
 
 
361
 
                                if conn and mapping_rec['external_update_method']:
 
354
                    exp_data = self.extdata_from_oevals(cr, uid, ext_ref_id, record_data, mapping_lines, defaults, context)
 
355
                    #Check if export for this referential demands a create or update
 
356
                    rec_check_ids = self.pool.get('ir.model.data').search(cr, uid, [('model', '=', self._name), ('res_id', '=', record_data['id']), ('module', 'ilike', 'extref'), ('external_referential_id', '=', ext_ref_id)])
 
357
                    #rec_check_ids will indicate if the product already has a mapping record with ext system
 
358
                    mapping_id = self.pool.get('external.mapping').search(cr, uid, [('model', '=', self._name), ('referential_id', '=', ext_ref_id)])
 
359
                    if mapping_id and len(mapping_id) == 1:
 
360
                        mapping_rec = self.pool.get('external.mapping').read(cr, uid, mapping_id[0], ['external_update_method', 'external_create_method'])
 
361
                        conn = context.get('conn_obj', False)
 
362
                        if rec_check_ids and mapping_rec and len(rec_check_ids) == 1:
 
363
                            ext_id = self.oeid_to_extid(cr, uid, record_data['id'], ext_ref_id, context)
 
364
 
 
365
                            if not context.get('force', False):#TODO rename this context's key in 'no_date_check' or something like that
 
366
                                #Record exists, check if update is required, for that collect last update times from ir.data & record
 
367
                                last_exported_times = self.pool.get('ir.model.data').read(cr, uid, rec_check_ids[0], ['write_date', 'create_date'])
 
368
                                last_exported_time = last_exported_times.get('write_date', False) or last_exported_times.get('create_date', False)
 
369
                                this_record_times = self.read(cr, uid, record_data['id'], ['write_date', 'create_date'])
 
370
                                last_updated_time = this_record_times.get('write_date', False) or this_record_times.get('create_date', False)
 
371
 
 
372
                                if not last_updated_time: #strangely seems that on inherits structure, write_date/create_date are False for children
 
373
                                    cr.execute("select write_date, create_date from %s where id=%s;" % (self._name.replace('.', '_'), record_data['id']))
 
374
                                    read = cr.fetchone()
 
375
                                    last_updated_time = read[0] and read[0].split('.')[0] or read[1] and read[1].split('.')[0] or False
 
376
 
 
377
                                if last_updated_time and last_exported_time:
 
378
                                    last_exported_time = datetime.datetime.fromtimestamp(time.mktime(time.strptime(last_exported_time, '%Y-%m-%d %H:%M:%S')))
 
379
                                    last_updated_time = datetime.datetime.fromtimestamp(time.mktime(time.strptime(last_updated_time, '%Y-%m-%d %H:%M:%S')))
 
380
                                    if last_exported_time + datetime.timedelta(seconds=1) > last_updated_time:
 
381
                                        continue
 
382
 
 
383
                            if conn and mapping_rec['external_update_method']:
 
384
                                try:
362
385
                                    self.ext_update(cr, uid, exp_data, conn, mapping_rec['external_update_method'], record_data['id'], ext_id, rec_check_ids[0], mapping_rec['external_create_method'], context)
363
386
                                    write_ids.append(record_data['id'])
364
387
                                    #Just simply write to ir.model.data to update the updated time
366
389
                                                            'res_id': record_data['id'],
367
390
                                                          }
368
391
                                    self.pool.get('ir.model.data').write(cr, uid, rec_check_ids[0], ir_model_data_vals)
 
392
                                    report_line_obj.log_success(cr, uid, self._name, 'export',
 
393
                                                                ext_ref_id, res_id=record_data['id'],
 
394
                                                                external_id=ext_id, defaults=defaults,
 
395
                                                                context=context)
369
396
                                    logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Updated in External Ref %s from OpenERP with external_id %s and OpenERP id %s successfully" %(self._name, ext_id, record_data['id']))
370
 
                            else:
371
 
                                #Record needs to be created
372
 
                                if conn and mapping_rec['external_create_method']:
 
397
                                except Exception, err:
 
398
                                    report_line_obj.log_failed(cr, uid, self._name, 'export',
 
399
                                                               ext_ref_id, res_id=record_data['id'],
 
400
                                                               external_id=ext_id, exception=err,
 
401
                                                               defaults=defaults, context=context)
 
402
                        else:
 
403
                            #Record needs to be created
 
404
                            if conn and mapping_rec['external_create_method']:
 
405
                                try:
373
406
                                    crid = self.ext_create(cr, uid, exp_data, conn, mapping_rec['external_create_method'], record_data['id'], context)
374
407
                                    create_ids.append(record_data['id'])
375
408
                                    ir_model_data_vals = {
380
413
                                                            'module': 'extref/' + self.pool.get('external.referential').read(cr, uid, ext_ref_id, ['name'])['name']
381
414
                                                          }
382
415
                                    self.pool.get('ir.model.data').create(cr, uid, ir_model_data_vals)
 
416
                                    report_line_obj.log_success(cr, uid, self._name, 'export',
 
417
                                                                ext_ref_id, res_id=record_data['id'],
 
418
                                                                external_id=crid, defaults=defaults,
 
419
                                                                context=context)
383
420
                                    logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Created in External Ref %s from OpenERP with external_id %s and OpenERP id %s successfully" %(self._name, crid, record_data['id']))
384
 
                            cr.commit()
385
 
                            
386
 
        return {'create_ids': create_ids, 'write_ids': write_ids}
387
 
 
388
 
 
389
 
    def can_create_on_update_failure(self, error, data, context):
390
 
        return True
391
 
 
392
 
    def ext_create(self, cr, uid, data, conn, method, oe_id, context):
393
 
        return conn.call(method, data)
394
 
    
395
 
    def try_ext_update(self, cr, uid, data, conn, method, oe_id, external_id, ir_model_data_id, create_method, context):
396
 
        return conn.call(method, [external_id, data])
397
 
    
398
 
    def ext_update(self, cr, uid, data, conn, method, oe_id, external_id, ir_model_data_id, create_method, context):
399
 
        try:
400
 
            self.try_ext_update(cr, uid, data, conn, method, oe_id, external_id, ir_model_data_id, create_method, context)
401
 
        except Exception, e:
402
 
            logger = netsvc.Logger()
403
 
            logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "UPDATE ERROR: %s" % e)
404
 
            if self.can_create_on_update_failure(e, data, context):
405
 
                logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "may be the resource doesn't exist any more in the external referential, trying to re-create a new one")
406
 
                crid = self.ext_create(cr, uid, data, conn, create_method, oe_id, context)
407
 
                self.pool.get('ir.model.data').write(cr, uid, ir_model_data_id, {'name': self.prefixed_id(crid)})
408
 
                return crid
409
 
            
410
 
        
 
421
                                except Exception, err:
 
422
                                    report_line_obj.log_failed(cr, uid, self._name, 'export',
 
423
                                                               ext_ref_id, res_id=record_data['id'],
 
424
                                                               exception=err, defaults=defaults,
 
425
                                                               context=context)
 
426
                        cr.commit()
 
427
 
 
428
    return {'create_ids': create_ids, 'write_ids': write_ids}
 
429
 
 
430
def retry_export(self, cr, uid, id, external_referential_id, defaults=None, context=None):
 
431
    """ When we export again a previously failed export
 
432
    """
 
433
    conn = self.external_connection(
 
434
        cr,
 
435
        uid,
 
436
        self.pool.get('external.referential').
 
437
        browse(cr, uid, external_referential_id))
 
438
    context['conn_obj'] = conn
 
439
    return self.ext_export(cr, uid, [id], [external_referential_id], defaults, context)
 
440
 
 
441
def can_create_on_update_failure(self, error, data, context):
 
442
    return True
 
443
 
 
444
def ext_create(self, cr, uid, data, conn, method, oe_id, context):
 
445
    return conn.call(method, data)
 
446
 
 
447
def try_ext_update(self, cr, uid, data, conn, method, oe_id, external_id, ir_model_data_id, create_method, context):
 
448
    return conn.call(method, [external_id, data])
 
449
 
 
450
def ext_update(self, cr, uid, data, conn, method, oe_id, external_id, ir_model_data_id, create_method, context):
 
451
    try:
 
452
        self.try_ext_update(cr, uid, data, conn, method, oe_id, external_id, ir_model_data_id, create_method, context)
 
453
    except Exception, e:
 
454
        logger = netsvc.Logger()
 
455
        logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "UPDATE ERROR: %s" % e)
 
456
        if self.can_create_on_update_failure(e, data, context):
 
457
            logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "may be the resource doesn't exist any more in the external referential, trying to re-create a new one")
 
458
            crid = self.ext_create(cr, uid, data, conn, create_method, oe_id, context)
 
459
            self.pool.get('ir.model.data').write(cr, uid, ir_model_data_id, {'name': self.prefixed_id(crid)})
 
460
            return crid
 
461
 
 
462
def report_action_mapping(self, cr, uid, context=None):
 
463
        """
 
464
        For each action logged in the reports, we associate
 
465
        the method to launch when we replay the action.
 
466
        """
 
467
        mapping = {
 
468
            'export': self.retry_export,
 
469
            'import': self.retry_import,
 
470
        }
 
471
        return mapping
 
472
 
 
473
 
 
474
osv.osv.read_w_order = read_w_order
 
475
osv.osv.browse_w_order = browse_w_order
 
476
osv.osv.prefixed_id = prefixed_id
 
477
osv.osv.id_from_prefixed_id = id_from_prefixed_id
 
478
osv.osv.get_last_imported_external_id = get_last_imported_external_id
 
479
osv.osv.get_modified_ids = get_modified_ids
 
480
osv.osv.external_connection = external_connection
 
481
osv.osv.oeid_to_extid = oeid_to_extid
 
482
osv.osv.extid_to_existing_oeid = extid_to_existing_oeid
 
483
osv.osv.extid_to_oeid = extid_to_oeid
 
484
osv.osv.oevals_from_extdata = oevals_from_extdata
 
485
osv.osv.get_external_data = get_external_data
 
486
osv.osv.add_external_reference = add_external_reference
 
487
osv.osv.ext_import = ext_import
 
488
osv.osv.oe_update = oe_update
 
489
osv.osv.oe_create = oe_create
 
490
osv.osv.extdata_from_oevals = extdata_from_oevals
 
491
osv.osv.ext_export = ext_export
 
492
osv.osv.retry_export = retry_export
 
493
osv.osv.can_create_on_update_failure = can_create_on_update_failure
 
494
osv.osv.ext_create = ext_create
 
495
osv.osv.try_ext_update = try_ext_update
 
496
osv.osv.ext_update = ext_update
 
497
osv.osv.report_action_mapping = report_action_mapping