[IMP] import/export : added Database ID, ID
[odoo/odoo.git] / bin / osv / orm.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 #
24 # Object relationnal mapping to postgresql module
25 #    . Hierarchical structure
26 #    . Constraints consistency, validations
27 #    . Object meta Data depends on its status
28 #    . Optimised processing by complex query (multiple actions at once)
29 #    . Default fields value
30 #    . Permissions optimisation
31 #    . Persistant object: DB postgresql
32 #    . Datas conversions
33 #    . Multi-level caching system
34 #    . 2 different inheritancies
35 #    . Fields:
36 #         - classicals (varchar, integer, boolean, ...)
37 #         - relations (one2many, many2one, many2many)
38 #         - functions
39 #
40 #
41
42 import time
43 import calendar
44 import types
45 import string
46 import netsvc
47 import re
48
49 import pickle
50
51 import fields
52 import tools
53
54 import sys
55
56 try:
57     from xml import dom, xpath
58 except ImportError:
59     sys.stderr.write("ERROR: Import xpath module\n")
60     sys.stderr.write("ERROR: Try to install the old python-xml package\n")
61     sys.stderr.write('On Ubuntu Jaunty, try this: sudo cp /usr/lib/python2.6/dist-packages/oldxml/_xmlplus/utils/boolean.so /usr/lib/python2.5/site-packages/oldxml/_xmlplus/utils\n')
62     raise
63
64 from tools.config import config
65
66 regex_order = re.compile('^([a-z0-9_]+( *desc| *asc)?( *, *|))+$', re.I)
67
68 def last_day_of_current_month():
69     import datetime
70     import calendar
71     today = datetime.date.today()
72     last_day = str(calendar.monthrange(today.year, today.month)[1])
73     return time.strftime('%Y-%m-' + last_day)
74
75 def intersect(la, lb):
76     return filter(lambda x: x in lb, la)
77
78
79 class except_orm(Exception):
80     def __init__(self, name, value):
81         self.name = name
82         self.value = value
83         self.args = (name, value)
84
85 class BrowseRecordError(Exception):
86     pass
87
88 # Readonly python database object browser
89 class browse_null(object):
90
91     def __init__(self):
92         self.id = False
93
94     def __getitem__(self, name):
95         return False
96
97     def __getattr__(self, name):
98         return False  # XXX: return self ?
99
100     def __int__(self):
101         return False
102
103     def __str__(self):
104         return ''
105
106     def __nonzero__(self):
107         return False
108
109     def __unicode__(self):
110         return u''
111
112
113 #
114 # TODO: execute an object method on browse_record_list
115 #
116 class browse_record_list(list):
117
118     def __init__(self, lst, context=None):
119         if not context:
120             context = {}
121         super(browse_record_list, self).__init__(lst)
122         self.context = context
123
124
125 class browse_record(object):
126     def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}):
127         '''
128         table : the object (inherited from orm)
129         context : a dictionary with an optional context
130         '''
131         if not context:
132             context = {}
133         self._list_class = list_class or browse_record_list
134         self._cr = cr
135         self._uid = uid
136         self._id = id
137         self._table = table
138         self._table_name = self._table._name
139         self._context = context
140         self._fields_process = fields_process
141
142         cache.setdefault(table._name, {})
143         self._data = cache[table._name]
144
145         if not (id and isinstance(id, (int, long,))):
146             raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
147 #        if not table.exists(cr, uid, id, context):
148 #            raise BrowseRecordError(_('Object %s does not exists') % (self,))
149
150         if id not in self._data:
151             self._data[id] = {'id': id}
152
153         self._cache = cache
154
155     def __getitem__(self, name):
156         if name == 'id':
157             return self._id
158         if name not in self._data[self._id]:
159             # build the list of fields we will fetch
160
161             # fetch the definition of the field which was asked for
162             if name in self._table._columns:
163                 col = self._table._columns[name]
164             elif name in self._table._inherit_fields:
165                 col = self._table._inherit_fields[name][2]
166             elif hasattr(self._table, name):
167                 if isinstance(getattr(self._table, name), (types.MethodType, types.LambdaType, types.FunctionType)):
168                     return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv)
169                 else:
170                     return getattr(self._table, name)
171             else:
172                 logger = netsvc.Logger()
173                 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name))
174                 return False
175
176             # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
177             if col._classic_write:
178                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
179                 ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
180                 # gen the list of inherited fields
181                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
182                 # complete the field list with the inherited fields which are classic or many2one
183                 ffields += filter(lambda x: x[1]._classic_write, inherits)
184             # otherwise we fetch only that field
185             else:
186                 ffields = [(name, col)]
187             ids = filter(lambda id: name not in self._data[id], self._data.keys())
188             # read the data
189             fffields = map(lambda x: x[0], ffields)
190             datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write")
191             if self._fields_process:
192                 lang = self._context.get('lang', 'en_US') or 'en_US'
193                 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
194                 if not lang_obj_ids:
195                     raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
196                 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
197                 for n, f in ffields:
198                     if f._type in self._fields_process:
199                         for d in datas:
200                             d[n] = self._fields_process[f._type](d[n])
201                             if (d[n] is not None) and (d[n] is not False):
202                                 d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj)
203
204
205             # create browse records for 'remote' objects
206             for data in datas:
207                 for n, f in ffields:
208                     if f._type in ('many2one', 'one2one'):
209                         if data[n]:
210                             obj = self._table.pool.get(f._obj)
211                             compids = False
212                             if type(data[n]) in (type([]),type( (1,) )):
213                                 ids2 = data[n][0]
214                             else:
215                                 ids2 = data[n]
216                             if ids2:
217                                 data[n] = browse_record(self._cr, self._uid, ids2, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
218                             else:
219                                 data[n] = browse_null()
220                         else:
221                             data[n] = browse_null()
222                     elif f._type in ('one2many', 'many2many') and len(data[n]):
223                         data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
224                 self._data[data['id']].update(data)
225         return self._data[self._id][name]
226
227     def __getattr__(self, name):
228 #       raise an AttributeError exception.
229         return self[name]
230
231     def __contains__(self, name):
232         return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
233
234     def __hasattr__(self, name):
235         return name in self
236
237     def __int__(self):
238         return self._id
239
240     def __str__(self):
241         return "browse_record(%s, %d)" % (self._table_name, self._id)
242
243     def __eq__(self, other):
244         return (self._table_name, self._id) == (other._table_name, other._id)
245
246     def __ne__(self, other):
247         return (self._table_name, self._id) != (other._table_name, other._id)
248
249     # we need to define __unicode__ even though we've already defined __str__
250     # because we have overridden __getattr__
251     def __unicode__(self):
252         return unicode(str(self))
253
254     def __hash__(self):
255         return hash((self._table_name, self._id))
256
257     __repr__ = __str__
258
259
260 def get_pg_type(f):
261     '''
262     returns a tuple
263     (type returned by postgres when the column was created, type expression to create the column)
264     '''
265
266     type_dict = {
267             fields.boolean: 'bool',
268             fields.integer: 'int4',
269             fields.integer_big: 'int8',
270             fields.text: 'text',
271             fields.date: 'date',
272             fields.time: 'time',
273             fields.datetime: 'timestamp',
274             fields.binary: 'bytea',
275             fields.many2one: 'int4',
276             }
277     if type(f) in type_dict:
278         f_type = (type_dict[type(f)], type_dict[type(f)])
279     elif isinstance(f, fields.float):
280         if f.digits:
281             f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1]))
282         else:
283             f_type = ('float8', 'DOUBLE PRECISION')
284     elif isinstance(f, (fields.char, fields.reference)):
285         f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
286     elif isinstance(f, fields.selection):
287         if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
288             f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
289         elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
290             f_size = -1
291         else:
292             f_size = (hasattr(f, 'size') and f.size) or 16
293
294         if f_size == -1:
295             f_type = ('int4', 'INTEGER')
296         else:
297             f_type = ('varchar', 'VARCHAR(%d)' % f_size)
298     elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict:
299         t = eval('fields.'+(f._type))
300         f_type = (type_dict[t], type_dict[t])
301     elif isinstance(f, fields.function) and f._type == 'float':
302         f_type = ('float8', 'DOUBLE PRECISION')
303     elif isinstance(f, fields.function) and f._type == 'selection':
304         f_type = ('text', 'text')
305     elif isinstance(f, fields.function) and f._type == 'char':
306         f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
307     else:
308         logger = netsvc.Logger()
309         logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
310         f_type = None
311     return f_type
312
313
314 class orm_template(object):
315     _name = None
316     _columns = {}
317     _constraints = []
318     _defaults = {}
319     _rec_name = 'name'
320     _parent_name = 'parent_id'
321     _parent_store = False
322     _parent_order = False
323     _date_name = 'date'
324     _order = 'id'
325     _sequence = None
326     _description = None
327     _inherits = {}
328     _table = None
329     _invalids = set()
330
331     CONCURRENCY_CHECK_FIELD = '__last_update'
332
333     def _field_create(self, cr, context={}):
334         cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
335         if not cr.rowcount:
336             cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
337             model_id = cr.fetchone()[0]
338             cr.execute("INSERT INTO ir_model (id,model, name, info,state) VALUES (%s, %s, %s, %s, %s)", (model_id, self._name, self._description, self.__doc__, 'base'))
339         else:
340             model_id = cr.fetchone()[0]
341         if 'module' in context:
342             name_id = 'model_'+self._name.replace('.','_')
343             cr.execute('select * from ir_model_data where name=%s and res_id=%s', (name_id,model_id))
344             if not cr.rowcount:
345                 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
346                     (name_id, context['module'], 'ir.model', model_id)
347                 )
348
349         cr.commit()
350
351         cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
352         cols = {}
353         for rec in cr.dictfetchall():
354             cols[rec['name']] = rec
355
356         for (k, f) in self._columns.items():
357             vals = {
358                 'model_id': model_id,
359                 'model': self._name,
360                 'name': k,
361                 'field_description': f.string.replace("'", " "),
362                 'ttype': f._type,
363                 'relation': f._obj or 'NULL',
364                 'view_load': (f.view_load and 1) or 0,
365                 'select_level': str(f.select or 0),
366                 'readonly':(f.readonly and 1) or 0,
367                 'required':(f.required and 1) or 0,
368             }
369             if k not in cols:
370                 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
371                 id = cr.fetchone()[0]
372                 vals['id'] = id
373                 cr.execute("""INSERT INTO ir_model_fields (
374                     id, model_id, model, name, field_description, ttype,
375                     relation,view_load,state,select_level
376                 ) VALUES (
377                     %s,%s,%s,%s,%s,%s,%s,%s,%s,%s
378                 )""", (
379                     id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
380                      vals['relation'], bool(vals['view_load']), 'base',
381                     vals['select_level']
382                 ))
383                 if 'module' in context:
384                     cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
385                         (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id)
386                     )
387             else:
388                 for key, val in vals.items():
389                     if cols[k][key] != vals[key]:
390                         cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
391                         cr.commit()
392                         cr.execute("""UPDATE ir_model_fields SET
393                             model_id=%s, field_description=%s, ttype=%s, relation=%s,
394                             view_load=%s, select_level=%s, readonly=%s ,required=%s
395                         WHERE
396                             model=%s AND name=%s""", (
397                                 vals['model_id'], vals['field_description'], vals['ttype'],
398                                 vals['relation'], bool(vals['view_load']),
399                                 vals['select_level'], bool(vals['readonly']),bool(vals['required']), vals['model'], vals['name']
400                             ))
401                         continue
402         cr.commit()
403
404     def _auto_init(self, cr, context={}):
405         self._field_create(cr, context)
406
407     def __init__(self, cr):
408         if not self._name and not hasattr(self, '_inherit'):
409             name = type(self).__name__.split('.')[0]
410             msg = "The class %s has to have a _name attribute" % name
411
412             logger = netsvc.Logger()
413             logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
414             raise except_orm('ValueError', msg )
415
416         if not self._description:
417             self._description = self._name
418         if not self._table:
419             self._table = self._name.replace('.', '_')
420
421     def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
422         if not context:
423             context = {}
424         self._list_class = list_class or browse_record_list
425         cache = {}
426         # need to accepts ints and longs because ids coming from a method
427         # launched by button in the interface have a type long...
428         if isinstance(select, (int, long)):
429             return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
430         elif isinstance(select, list):
431             return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context)
432         else:
433             return browse_null()
434
435     def __export_row(self, cr, uid, row, fields, context=None):
436         
437         def check_type(field_type):
438             if field_type == 'float':
439                 return 0.0
440             elif field_type == 'integer':
441                 return 0
442             elif field_type == 'boolean':   
443                 return False                                
444             return ''
445         
446         lines = []
447         data = map(lambda x: '', range(len(fields)))
448         done = []
449         for fpos in range(len(fields)):
450             f = fields[fpos]
451             if f:
452                 r = row
453                 i = 0
454                 while i < len(f):
455                     if f[i] == 'db_id':
456                         r = r['id']                        
457                     elif f[i] == 'id':                        
458                         model_data = self.pool.get('ir.model.data')
459                         data_ids = model_data.search(cr, uid, [('model','=',r._table_name),('res_id','=',r['id'])])
460                         if len(data_ids):
461                             d = model_data.read(cr, uid, data_ids, ['name','module'])[0]
462                             if d['module']:
463                                 r = '%s.%s'%(d['module'],d['name'])
464                             else:
465                                 r = d['name']
466                         else:
467                             break
468                     else:
469                         r = r[f[i]]                    
470                     if not r:
471                         if f[i] in self._columns: 
472                             r = check_type(self._columns[f[i]]._type)
473                         elif f[i] in self._inherit_fields:
474                             r = check_type(self._inherit_fields[f[i]][2]._type)                        
475                         data[fpos] = r                        
476                         break
477                     if isinstance(r, (browse_record_list, list)):
478                         first = True
479                         fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
480                                 or [], fields)
481                         if fields2 in done:
482                             break
483                         done.append(fields2)
484                         for row2 in r:
485                             lines2 = self.__export_row(cr, uid, row2, fields2,
486                                     context)
487                             if first:
488                                 for fpos2 in range(len(fields)):
489                                     if lines2 and lines2[0][fpos2]:
490                                         data[fpos2] = lines2[0][fpos2]
491                                 lines += lines2[1:]
492                                 first = False
493                             else:
494                                 lines += lines2
495                         break
496                     i += 1
497                 if i == len(f):
498                     if isinstance(r, browse_record):
499                         r = r.name
500                     data[fpos] = tools.ustr(r or '')
501         return [data] + lines
502
503     def export_data(self, cr, uid, ids, fields_to_export, context=None, imp_comp= False):
504         if not context:
505             context = {}
506         cols = self._columns.copy()
507         for f in self._inherit_fields:
508             cols.update({f: self._inherit_fields[f][2]})        
509         fields_to_export = map(lambda x: x.split('/'), fields_to_export)
510         fields_export = fields_to_export+[]        
511         warning = ''  
512         warning_fields = []      
513         for field in fields_export:
514             if imp_comp and len(field)>1:                              
515                 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
516             elif len (field) <=1:
517                 if imp_comp and cols.get(field and field[0],False):
518                     if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
519                                      or isinstance(cols[field[0]], fields.related)\
520                                      or isinstance(cols[field[0]], fields.one2many)):                        
521                         warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
522         datas = []
523         if imp_comp and len(warning_fields):
524             warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' %('\n'.join(warning_fields))        
525             cr.rollback()
526             return {'warning' : warning}
527         for row in self.browse(cr, uid, ids, context):
528             datas += self.__export_row(cr, uid, row, fields_to_export, context)
529         return {'datas':datas}
530
531     def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
532         if not context:
533             context = {}
534         fields = map(lambda x: x.split('/'), fields)
535         logger = netsvc.Logger()
536         ir_model_data_obj = self.pool.get('ir.model.data')
537         def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
538             line = datas[position]
539             row = {}
540             translate = {}
541             todo = []
542             warning = ''
543             data_id = False
544             data_res_id = False
545             is_xml_id = False
546             is_db_id = False
547             ir_model_data_obj = self.pool.get('ir.model.data')
548             #
549             # Import normal fields
550             #
551             for i in range(len(fields)):
552                 if i >= len(line):
553                     raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
554                 if not line[i]:
555                     continue
556                 field = fields[i]                
557                 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
558                     res_id = False
559                     if line[i]:
560                         if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
561                             res_id = []
562                             for word in line[i].split(config.get('csv_internal_sep')):
563                                 if '.' in word:
564                                     module, xml_id = word.rsplit('.', 1)
565                                 else:
566                                     module, xml_id = current_module, word                                
567                                 id = ir_model_data_obj._get_id(cr, uid, module,
568                                         xml_id)
569                                 res_id2 = ir_model_data_obj.read(cr, uid, [id],
570                                         ['res_id'])[0]['res_id']
571                                 if res_id2:
572                                     res_id.append(res_id2)
573                             if len(res_id):
574                                 res_id = [(6, 0, res_id)]
575                         else:
576                             if '.' in line[i]:
577                                 module, xml_id = line[i].rsplit('.', 1)
578                             else:
579                                 module, xml_id = current_module, line[i]                            
580                             id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
581                             res_id = ir_model_data_obj.read(cr, uid, [id],
582                                     ['res_id'])[0]['res_id']
583                     row[field[0][:-3]] = res_id or False
584                     continue
585                 if (len(field) == len(prefix)+1) and \
586                         len(field[len(prefix)].split(':lang=')) == 2:
587                     f, lang = field[len(prefix)].split(':lang=')
588                     translate.setdefault(lang, {})[f]=line[i] or False
589                     continue
590                 if (len(field) == len(prefix)+1) and \
591                         (prefix == field[0:len(prefix)]):
592                     if field[len(prefix)] == "id":  
593                         # XML ID                         
594                         db_id = False                 
595                         is_xml_id = data_id = line[i] 
596                         d =  data_id.split('.')
597                         module = len(d)>1 and d[0] or ''
598                         name = len(d)>1 and d[1] or d[0] 
599                         data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('name','=',name)])                    
600                         if len(data_ids):
601                             d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]                                                
602                             db_id = d['res_id']                       
603                         if is_db_id and not db_id:
604                            data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('res_id','=',is_db_id)])                     
605                            if not len(data_ids):
606                                ir_model_data_obj.create(cr, uid, {'module':module, 'model':model_name, 'name':name, 'res_id':is_db_id}) 
607                                db_id = is_db_id 
608                         if is_db_id and int(db_id) != int(is_db_id):                        
609                             warning += ("Id is not the same than existing one: " + str(is_db_id) + " !\n")
610                             logger.notifyChannel("import", netsvc.LOG_ERROR,
611                                     "Id is not the same than existing one: " + str(is_db_id) + ' !\n')
612                         continue
613
614                     if field[len(prefix)] == "db_id":
615                         # Database ID                        
616                         try:                                        
617                             line[i]= int(line[i])                    
618                         except Exception, e:
619                             warning += (str(e) +  "!\n")
620                             logger.notifyChannel("import", netsvc.LOG_ERROR,
621                                     str(e)  + '!\n')
622                             continue
623                         is_db_id = line[i]
624                         obj_model = self.pool.get(model_name)                        
625                         ids = obj_model.search(cr, uid, [('id','=',line[i])])
626                         if not len(ids):
627                             warning += ("Database ID doesn't exist: " + model_name + ": " + str(line[i]) + " !\n")
628                             logger.notifyChannel("import", netsvc.LOG_ERROR,
629                                     "Database ID doesn't exist: " + model_name + ": " + str(line[i]) + ' !\n')
630                             continue
631                         else:
632                             data_res_id = ids[0]
633                         data_ids = ir_model_data_obj.search(cr, uid, [('model','=',model_name),('res_id','=',line[i])])
634                         if len(data_ids):
635                             d = ir_model_data_obj.read(cr, uid, data_ids, ['name','module'])[0]                                                
636                             data_id = d['name']       
637                             if d['module']:
638                                 data_id = '%s.%s'%(d['module'],d['name'])
639                             else:
640                                 data_id = d['name']
641                         if is_xml_id and not data_id:
642                             data_id = is_xml_id                                     
643                         if is_xml_id and is_xml_id!=data_id:  
644                             warning += ("Id is not the same than existing one: " + str(line[i]) + " !\n")
645                             logger.notifyChannel("import", netsvc.LOG_ERROR,
646                                     "Id is not the same than existing one: " + str(line[i]) + ' !\n')  
647                                                            
648                         continue
649                     if fields_def[field[len(prefix)]]['type'] == 'integer':
650                         res = line[i] and int(line[i])
651                     elif fields_def[field[len(prefix)]]['type'] == 'boolean':
652                         res = line[i] and eval(line[i])
653                     elif fields_def[field[len(prefix)]]['type'] == 'float':
654                         res = line[i] and float(line[i])
655                     elif fields_def[field[len(prefix)]]['type'] == 'selection':
656                         res = False
657                         if isinstance(fields_def[field[len(prefix)]]['selection'],
658                                 (tuple, list)):
659                             sel = fields_def[field[len(prefix)]]['selection']
660                         else:
661                             sel = fields_def[field[len(prefix)]]['selection'](self,
662                                     cr, uid, context)
663                         for key, val in sel:
664                             if line[i] in [str(key),str(val)]: #Acepting key or value for selection field
665                                 res = key
666                                 break
667                         if line[i] and not res:
668                             logger.notifyChannel("import", netsvc.LOG_WARNING,
669                                     "key '%s' not found in selection field '%s'" % \
670                                             (line[i], field[len(prefix)]))
671                             
672                             warning += "Key/value '"+ str(line[i]) +"' not found in selection field '"+str(field[len(prefix)])+"'"
673                             
674                     elif fields_def[field[len(prefix)]]['type']=='many2one':
675                         res = False
676                         if line[i]:
677                             relation = fields_def[field[len(prefix)]]['relation']
678                             res2 = self.pool.get(relation).name_search(cr, uid,
679                                     line[i], [], operator='=')
680                             res = (res2 and res2[0][0]) or False
681                             if not res:
682                                 warning += ('Relation not found: ' + line[i] + \
683                                         ' on ' + relation + ' !\n')
684                                 logger.notifyChannel("import", netsvc.LOG_WARNING,
685                                         'Relation not found: ' + line[i] + \
686                                                 ' on ' + relation + ' !\n')
687                     elif fields_def[field[len(prefix)]]['type']=='many2many':
688                         res = []
689                         if line[i]:
690                             relation = fields_def[field[len(prefix)]]['relation']
691                             for word in line[i].split(config.get('csv_internal_sep')):
692                                 res2 = self.pool.get(relation).name_search(cr,
693                                         uid, word, [], operator='=')
694                                 res3 = (res2 and res2[0][0]) or False
695                                 if not res3:
696                                     warning += ('Relation not found: ' + \
697                                             line[i] + ' on '+relation + ' !\n')
698                                     logger.notifyChannel("import",
699                                             netsvc.LOG_WARNING,
700                                             'Relation not found: ' + line[i] + \
701                                                     ' on '+relation + ' !\n')
702                                 else:
703                                     res.append(res3)
704                             if len(res):
705                                 res = [(6, 0, res)]
706                     else:
707                         res = line[i] or False
708                     row[field[len(prefix)]] = res
709                 elif (prefix==field[0:len(prefix)]):
710                     if field[0] not in todo:
711                         todo.append(field[len(prefix)])
712             #
713             # Import one2many, many2many fields
714             #
715             nbrmax = 1
716             for field in todo:
717                 relation_obj = self.pool.get(fields_def[field]['relation'])
718                 newfd = relation_obj.fields_get(
719                         cr, uid, context=context)
720                 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)                              
721                 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res                  
722                 nbrmax = max(nbrmax, max2)
723                 warning = warning + w2         
724                 reduce(lambda x, y: x and y, newrow)       
725                 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
726                         [(0, 0, newrow)]) or []                
727                 i = max2
728                 while (position+i)<len(datas):
729                     ok = True
730                     for j in range(len(fields)):
731                         field2 = fields[j]
732                         if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
733                             ok = False
734                     if not ok:
735                         break
736
737                     (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
738                             self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
739                     warning = warning+w2
740                     if reduce(lambda x, y: x or y, newrow.values()):
741                         row[field].append((0, 0, newrow))                    
742                     i += max2
743                     nbrmax = max(nbrmax, i)
744
745             if len(prefix)==0:
746                 for i in range(max(nbrmax, 1)):
747                     #if datas:
748                     datas.pop(0)
749             result = (row, nbrmax, warning, translate, data_id, data_res_id)
750             return result
751
752         fields_def = self.fields_get(cr, uid, context=context)
753         done = 0
754
755         initial_size = len(datas)
756         if config.get('import_partial', False) and filename:
757             data = pickle.load(file(config.get('import_partial')))
758             original_value =  data.get(filename, 0)
759         counter = 0
760         while len(datas):
761             counter += 1
762             res = {}
763             #try:
764             (res, other, warning, translate, data_id, res_id) = \
765                     process_liness(self, datas, [], current_module, self._name, fields_def)
766             if warning:                        
767                 cr.rollback()
768                 return (-1, res, 'Line ' + str(counter) +' : ' + warning, '')
769             
770             try:
771                 id = ir_model_data_obj._update(cr, uid, self._name,
772                      current_module, res, xml_id=data_id, mode=mode,
773                      noupdate=noupdate, res_id=res_id)
774             except Exception, e:
775                 import psycopg2
776                 if isinstance(e,psycopg2.IntegrityError):
777                     msg= 'Insertion Failed!'
778                     for key in self.pool._sql_error.keys():
779                         if key in e[0]:
780                             msg = self.pool._sql_error[key]
781                             break
782                     return (-1, res,'Line ' + str(counter) +' : ' + msg,'' )
783                 
784             for lang in translate:
785                 context2 = context.copy()
786                 context2['lang'] = lang
787                 self.write(cr, uid, [id], translate[lang], context2)
788             if config.get('import_partial', False) and filename and (not (counter%100)) :
789                 data = pickle.load(file(config.get('import_partial')))
790                 data[filename] = initial_size - len(datas) + original_value
791                 pickle.dump(data, file(config.get('import_partial'),'wb'))
792                 cr.commit()
793
794             #except Exception, e:
795             #    logger.notifyChannel("import", netsvc.LOG_ERROR, e)
796             #    cr.rollback()
797             #    try:
798             #        return (-1, res, e[0], warning)
799             #    except:
800             #        return (-1, res, e[0], '')
801             done += 1
802         #
803         # TODO: Send a request with the result and multi-thread !
804         #
805         return (done, 0, 0, 0)
806
807     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
808         raise _('The read method is not implemented on this object !')
809
810     def get_invalid_fields(self,cr,uid):
811         return list(self._invalids)
812
813     def _validate(self, cr, uid, ids, context=None):
814         context = context or {}
815         lng = context.get('lang', False) or 'en_US'
816         trans = self.pool.get('ir.translation')
817         error_msgs = []
818         for constraint in self._constraints:
819             fun, msg, fields = constraint
820             if not fun(self, cr, uid, ids):
821                 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
822                 error_msgs.append(
823                         _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
824                 )
825                 self._invalids.update(fields)
826         if error_msgs:
827             cr.rollback()
828             raise except_orm('ValidateError', '\n'.join(error_msgs))
829         else:
830             self._invalids.clear()
831
832     def default_get(self, cr, uid, fields_list, context=None):
833         return {}
834
835     def perm_read(self, cr, user, ids, context=None, details=True):
836         raise _('The perm_read method is not implemented on this object !')
837
838     def unlink(self, cr, uid, ids, context=None):
839         raise _('The unlink method is not implemented on this object !')
840
841     def write(self, cr, user, ids, vals, context=None):
842         raise _('The write method is not implemented on this object !')
843
844     def create(self, cr, user, vals, context=None):
845         raise _('The create method is not implemented on this object !')
846
847     # returns the definition of each field in the object
848     # the optional fields parameter can limit the result to some fields
849     def fields_get_keys(self, cr, user, context=None, read_access=True):
850         if context is None:
851             context = {}
852         res = self._columns.keys()
853         for parent in self._inherits:
854             res.extend(self.pool.get(parent).fields_get_keys(cr, user, fields, context))
855         return res
856
857     def fields_get(self, cr, user, fields=None, context=None, read_access=True):
858         if context is None:
859             context = {}
860         res = {}
861         translation_obj = self.pool.get('ir.translation')
862         model_access_obj = self.pool.get('ir.model.access')
863         for parent in self._inherits:
864             res.update(self.pool.get(parent).fields_get(cr, user, fields, context))
865
866         if self._columns.keys():
867             for f in self._columns.keys():
868                 if fields and f not in fields:
869                     continue
870                 res[f] = {'type': self._columns[f]._type}
871                 for arg in ('string', 'readonly', 'states', 'size', 'required',
872                         'change_default', 'translate', 'help', 'select'):
873                     if getattr(self._columns[f], arg):
874                         res[f][arg] = getattr(self._columns[f], arg)
875                 if not read_access:
876                     res[f]['readonly'] = True
877                     res[f]['states'] = {}
878                 for arg in ('digits', 'invisible','filters'):
879                     if hasattr(self._columns[f], arg) \
880                             and getattr(self._columns[f], arg):
881                         res[f][arg] = getattr(self._columns[f], arg)
882
883                 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
884                 if res_trans:
885                     res[f]['string'] = res_trans
886                 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
887                 if help_trans:
888                     res[f]['help'] = help_trans
889
890                 if hasattr(self._columns[f], 'selection'):
891                     if isinstance(self._columns[f].selection, (tuple, list)):
892                         sel = self._columns[f].selection
893                         # translate each selection option
894                         sel2 = []
895                         for (key, val) in sel:
896                             val2 = None
897                             if val:
898                                 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
899                             sel2.append((key, val2 or val))
900                         sel = sel2
901                         res[f]['selection'] = sel
902                     else:
903                         # call the 'dynamic selection' function
904                         res[f]['selection'] = self._columns[f].selection(self, cr,
905                                 user, context)
906                 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
907                     res[f]['relation'] = self._columns[f]._obj
908                     res[f]['domain'] = self._columns[f]._domain
909                     res[f]['context'] = self._columns[f]._context
910         else:
911             #TODO : read the fields from the database
912             pass
913
914         if fields:
915             # filter out fields which aren't in the fields list
916             for r in res.keys():
917                 if r not in fields:
918                     del res[r]
919         return res
920
921     #
922     # Overload this method if you need a window title which depends on the context
923     #
924     def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
925         return False
926
927     def __view_look_dom(self, cr, user, node, view_id, context=None):
928         if not context:
929             context = {}
930         result = False
931         fields = {}
932         childs = True
933
934         if node.nodeType == node.ELEMENT_NODE and node.localName == 'field':
935             if node.hasAttribute('name'):
936                 attrs = {}
937                 try:
938                     if node.getAttribute('name') in self._columns:
939                         column = self._columns[node.getAttribute('name')]
940                     else:
941                         column = self._inherit_fields[node.getAttribute('name')][2]
942                 except:
943                     column = False
944
945                 if column:
946                     relation = column._obj
947                     childs = False
948                     views = {}
949                     for f in node.childNodes:
950                         if f.nodeType == f.ELEMENT_NODE and f.localName in ('form', 'tree', 'graph'):
951                             node.removeChild(f)
952                             ctx = context.copy()
953                             ctx['base_model_name'] = self._name
954                             xarch, xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, view_id, ctx)
955                             views[str(f.localName)] = {
956                                 'arch': xarch,
957                                 'fields': xfields
958                             }
959                     attrs = {'views': views}
960                     if node.hasAttribute('widget') and node.getAttribute('widget')=='selection':
961                         # We can not use the 'string' domain has it is defined according to the record !
962                         dom = None
963                         if column._domain and not isinstance(column._domain, (str, unicode)):
964                             dom = column._domain
965                         attrs['selection'] = self.pool.get(relation).name_search(cr, user, '', dom, context=context)
966                         if (node.hasAttribute('required') and not int(node.getAttribute('required'))) or not column.required:
967                             attrs['selection'].append((False,''))
968                 fields[node.getAttribute('name')] = attrs
969
970         elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form', 'tree'):
971             result = self.view_header_get(cr, user, False, node.localName, context)
972             if result:
973                 node.setAttribute('string', result)
974
975         elif node.nodeType==node.ELEMENT_NODE and node.localName == 'calendar':
976             for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
977                 if node.hasAttribute(additional_field) and node.getAttribute(additional_field):
978                     fields[node.getAttribute(additional_field)] = {}
979
980         if node.nodeType == node.ELEMENT_NODE and node.hasAttribute('groups'):
981             if node.getAttribute('groups'):
982                 groups = node.getAttribute('groups').split(',')
983                 readonly = False
984                 access_pool = self.pool.get('ir.model.access')
985                 for group in groups:
986                     readonly = readonly or access_pool.check_groups(cr, user, group)
987                 if not readonly:
988                     node.setAttribute('invisible', '1')
989             node.removeAttribute('groups')
990
991         if node.nodeType == node.ELEMENT_NODE:
992             # translate view
993             if ('lang' in context) and not result:
994                 if node.hasAttribute('string') and node.getAttribute('string'):
995                     trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.getAttribute('string').encode('utf8'))
996                     if not trans and ('base_model_name' in context):
997                         trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.getAttribute('string').encode('utf8'))
998                     if trans:
999                         node.setAttribute('string', trans)
1000                 if node.hasAttribute('sum') and node.getAttribute('sum'):
1001                     trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.getAttribute('sum').encode('utf8'))
1002                     if trans:
1003                         node.setAttribute('sum', trans)
1004
1005         if childs:
1006             for f in node.childNodes:
1007                 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1008
1009         return fields
1010
1011     def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1012         fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1013
1014         rolesobj = self.pool.get('res.roles')
1015         usersobj = self.pool.get('res.users')
1016
1017         buttons = (n for n in node.getElementsByTagName('button') if n.getAttribute('type') != 'object')
1018         for button in buttons:
1019             ok = True
1020             if user != 1:   # admin user has all roles
1021                 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
1022                 cr.execute("select role_id from wkf_transition where signal=%s", (button.getAttribute('name'),))
1023                 roles = cr.fetchall()
1024                 for role in roles:
1025                     if role[0]:
1026                         ok = ok and rolesobj.check(cr, user, user_roles, role[0])
1027
1028             if not ok:
1029                 button.setAttribute('readonly', '1')
1030             else:
1031                 button.setAttribute('readonly', '0')
1032
1033         arch = node.toxml(encoding="utf-8").replace('\t', '')
1034         fields = self.fields_get(cr, user, fields_def.keys(), context)
1035         for field in fields_def:
1036             if field == 'id':
1037                 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1038                 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1039             elif field in fields:
1040                 fields[field].update(fields_def[field])
1041             else:
1042                 cr.execute('select name, model from ir_ui_view where (id=%s or inherit_id=%s) and arch like %s', (view_id, view_id, '%%%s%%' % field))
1043                 res = cr.fetchall()[:]
1044                 model = res[0][1]
1045                 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1046                 msg = "\n * ".join([r[0] for r in res])
1047                 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1048                 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1049                 raise except_orm('View error', msg)
1050
1051         return arch, fields
1052
1053     def __get_default_calendar_view(self):
1054         """Generate a default calendar view (For internal use only).
1055         """
1056
1057         arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1058                 '<calendar string="%s" date_start="%s"') % (self._description, self._date_name)
1059
1060         if 'user_id' in self._columns:
1061             arch += ' color="user_id"'
1062
1063         elif 'partner_id' in self._columns:
1064             arch += ' color="partner_id"'
1065
1066         if 'date_stop' in self._columns:
1067             arch += ' date_stop="date_stop"'
1068
1069         elif 'date_end' in self._columns:
1070             arch += ' date_stop="date_end"'
1071
1072         elif 'date_delay' in self._columns:
1073             arch += ' date_delay="date_delay"'
1074
1075         elif 'planned_hours' in self._columns:
1076             arch += ' date_delay="planned_hours"'
1077
1078         arch += ('>\n'
1079                  '  <field name="%s"/>\n'
1080                  '</calendar>') % (self._rec_name)
1081
1082         return arch
1083
1084     #
1085     # if view_id, view_type is not required
1086     #
1087     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False):
1088         if not context:
1089             context = {}
1090
1091         def encode(s):
1092             if isinstance(s, unicode):
1093                 return s.encode('utf8')
1094             return s
1095
1096         def _inherit_apply(src, inherit):
1097             def _find(node, node2):
1098                 if node2.nodeType == node2.ELEMENT_NODE and node2.localName == 'xpath':
1099                     res = xpath.Evaluate(node2.getAttribute('expr'), node)
1100                     return res and res[0]
1101                 else:
1102                     if node.nodeType == node.ELEMENT_NODE and node.localName == node2.localName:
1103                         res = True
1104                         for attr in node2.attributes.keys():
1105                             if attr == 'position':
1106                                 continue
1107                             if node.hasAttribute(attr):
1108                                 if node.getAttribute(attr)==node2.getAttribute(attr):
1109                                     continue
1110                             res = False
1111                         if res:
1112                             return node
1113                     for child in node.childNodes:
1114                         res = _find(child, node2)
1115                         if res:
1116                             return res
1117                 return None
1118
1119
1120             doc_src = dom.minidom.parseString(encode(src))
1121             doc_dest = dom.minidom.parseString(encode(inherit))
1122             toparse = doc_dest.childNodes
1123             while len(toparse):
1124                 node2 = toparse.pop(0)
1125                 if not node2.nodeType == node2.ELEMENT_NODE:
1126                     continue
1127                 if node2.localName == 'data':
1128                     toparse += node2.childNodes
1129                     continue
1130                 node = _find(doc_src, node2)
1131                 if node:
1132                     pos = 'inside'
1133                     if node2.hasAttribute('position'):
1134                         pos = node2.getAttribute('position')
1135                     if pos == 'replace':
1136                         parent = node.parentNode
1137                         for child in node2.childNodes:
1138                             if child.nodeType == child.ELEMENT_NODE:
1139                                 parent.insertBefore(child, node)
1140                         parent.removeChild(node)
1141                     else:
1142                         sib = node.nextSibling
1143                         for child in node2.childNodes:
1144                             if child.nodeType == child.ELEMENT_NODE:
1145                                 if pos == 'inside':
1146                                     node.appendChild(child)
1147                                 elif pos == 'after':
1148                                     node.parentNode.insertBefore(child, sib)
1149                                 elif pos=='before':
1150                                     node.parentNode.insertBefore(child, node)
1151                                 else:
1152                                     raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1153                 else:
1154                     attrs = ''.join([
1155                         ' %s="%s"' % (attr, node2.getAttribute(attr))
1156                         for attr in node2.attributes.keys()
1157                         if attr != 'position'
1158                     ])
1159                     tag = "<%s%s>" % (node2.localName, attrs)
1160                     raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1161             return doc_src.toxml(encoding="utf-8").replace('\t', '')
1162
1163         result = {'type': view_type, 'model': self._name}
1164
1165         ok = True
1166         model = True
1167         sql_res = False
1168         while ok:
1169             if view_id:
1170                 where = (model and (" and model='%s'" % (self._name,))) or ''
1171                 cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s'+where, (view_id,))
1172             else:
1173                 cr.execute('''SELECT
1174                         arch,name,field_parent,id,type,inherit_id
1175                     FROM
1176                         ir_ui_view
1177                     WHERE
1178                         model=%s AND
1179                         type=%s AND
1180                         inherit_id IS NULL
1181                     ORDER BY priority''', (self._name, view_type))
1182             sql_res = cr.fetchone()
1183             if not sql_res:
1184                 break
1185             ok = sql_res[5]
1186             view_id = ok or sql_res[3]
1187             model = False
1188
1189         # if a view was found
1190         if sql_res:
1191             result['type'] = sql_res[4]
1192             result['view_id'] = sql_res[3]
1193             result['arch'] = sql_res[0]
1194
1195             def _inherit_apply_rec(result, inherit_id):
1196                 # get all views which inherit from (ie modify) this view
1197                 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1198                 sql_inherit = cr.fetchall()
1199                 for (inherit, id) in sql_inherit:
1200                     result = _inherit_apply(result, inherit)
1201                     result = _inherit_apply_rec(result, id)
1202                 return result
1203
1204             result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
1205
1206             result['name'] = sql_res[1]
1207             result['field_parent'] = sql_res[2] or False
1208         else:
1209             # otherwise, build some kind of default view
1210             if view_type == 'form':
1211                 res = self.fields_get(cr, user, context=context)
1212                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1213                 '''<form string="%s">''' % (self._description,)
1214                 for x in res:
1215                     if res[x]['type'] not in ('one2many', 'many2many'):
1216                         xml += '<field name="%s"/>' % (x,)
1217                         if res[x]['type'] == 'text':
1218                             xml += "<newline/>"
1219                 xml += "</form>"
1220             elif view_type == 'tree':
1221                 _rec_name = self._rec_name
1222                 if _rec_name not in self._columns:
1223                     _rec_name = self._columns.keys()[0]
1224                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1225                 '''<tree string="%s"><field name="%s"/></tree>''' \
1226                 % (self._description, self._rec_name)
1227             elif view_type == 'calendar':
1228                 xml = self.__get_default_calendar_view()
1229             else:
1230                 xml = ''
1231             result['arch'] = xml
1232             result['name'] = 'default'
1233             result['field_parent'] = False
1234             result['view_id'] = 0
1235
1236         doc = dom.minidom.parseString(encode(result['arch']))
1237         xarch, xfields = self.__view_look_dom_arch(cr, user, doc, view_id, context=context)
1238         result['arch'] = xarch
1239         result['fields'] = xfields
1240         if toolbar:
1241             def clean(x):
1242                 x = x[2]
1243                 for key in ('report_sxw_content', 'report_rml_content',
1244                         'report_sxw', 'report_rml',
1245                         'report_sxw_content_data', 'report_rml_content_data'):
1246                     if key in x:
1247                         del x[key]
1248                 return x
1249             ir_values_obj = self.pool.get('ir.values')
1250             resprint = ir_values_obj.get(cr, user, 'action',
1251                     'client_print_multi', [(self._name, False)], False,
1252                     context)
1253             resaction = ir_values_obj.get(cr, user, 'action',
1254                     'client_action_multi', [(self._name, False)], False,
1255                     context)
1256
1257             resrelate = ir_values_obj.get(cr, user, 'action',
1258                     'client_action_relate', [(self._name, False)], False,
1259                     context)
1260             resprint = map(clean, resprint)
1261             resaction = map(clean, resaction)
1262             resaction = filter(lambda x: not x.get('multi', False), resaction)
1263             resprint = filter(lambda x: not x.get('multi', False), resprint)
1264             resrelate = map(lambda x: x[2], resrelate)
1265
1266             for x in resprint+resaction+resrelate:
1267                 x['string'] = x['name']
1268
1269             result['toolbar'] = {
1270                 'print': resprint,
1271                 'action': resaction,
1272                 'relate': resrelate
1273             }
1274         return result
1275
1276     _view_look_dom_arch = __view_look_dom_arch
1277
1278     def search_count(self, cr, user, args, context=None):
1279         if not context:
1280             context = {}
1281         res = self.search(cr, user, args, context=context, count=True)
1282         if isinstance(res, list):
1283             return len(res)
1284         return res
1285
1286     def search(self, cr, user, args, offset=0, limit=None, order=None,
1287             context=None, count=False):
1288         raise _('The search method is not implemented on this object !')
1289
1290     def name_get(self, cr, user, ids, context=None):
1291         raise _('The name_get method is not implemented on this object !')
1292
1293     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=None):
1294         raise _('The name_search method is not implemented on this object !')
1295
1296     def copy(self, cr, uid, id, default=None, context=None):
1297         raise _('The copy method is not implemented on this object !')
1298
1299     def exists(self, cr, uid, id, context=None):
1300         raise _('The exists method is not implemented on this object !')
1301
1302     def read_string(self, cr, uid, id, langs, fields=None, context=None):
1303         res = {}
1304         res2 = {}
1305         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read')
1306         if not fields:
1307             fields = self._columns.keys() + self._inherit_fields.keys()
1308         for lang in langs:
1309             res[lang] = {'code': lang}
1310             for f in fields:
1311                 if f in self._columns:
1312                     res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1313                     if res_trans:
1314                         res[lang][f] = res_trans
1315                     else:
1316                         res[lang][f] = self._columns[f].string
1317         for table in self._inherits:
1318             cols = intersect(self._inherit_fields.keys(), fields)
1319             res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1320         for lang in res2:
1321             if lang in res:
1322                 res[lang]['code'] = lang
1323             for f in res2[lang]:
1324                 res[lang][f] = res2[lang][f]
1325         return res
1326
1327     def write_string(self, cr, uid, id, langs, vals, context=None):
1328         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write')
1329         for lang in langs:
1330             for field in vals:
1331                 if field in self._columns:
1332                     self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field])
1333         for table in self._inherits:
1334             cols = intersect(self._inherit_fields.keys(), vals)
1335             if cols:
1336                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1337         return True
1338
1339     def _check_removed_columns(self, cr, log=False):
1340         raise NotImplementedError()
1341
1342 class orm_memory(orm_template):
1343     _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
1344     _inherit_fields = {}
1345     _max_count = 200
1346     _max_hours = 1
1347     _check_time = 20
1348
1349     def __init__(self, cr):
1350         super(orm_memory, self).__init__(cr)
1351         self.datas = {}
1352         self.next_id = 0
1353         self.check_id = 0
1354         cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1355
1356     def vaccum(self, cr, uid):
1357         self.check_id += 1
1358         if self.check_id % self._check_time:
1359             return True
1360         tounlink = []
1361         max = time.time() - self._max_hours * 60 * 60
1362         for id in self.datas:
1363             if self.datas[id]['internal.date_access'] < max:
1364                 tounlink.append(id)
1365         self.unlink(cr, uid, tounlink)
1366         if len(self.datas)>self._max_count:
1367             sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1368             sorted.sort()
1369             ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1370             self.unlink(cr, uid, ids)
1371         return True
1372
1373     def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1374         if not context:
1375             context = {}
1376         if not fields_to_read:
1377             fields_to_read = self._columns.keys()
1378         result = []
1379         if self.datas:
1380             if isinstance(ids, (int, long)):
1381                 ids = [ids]
1382             for id in ids:
1383                 r = {'id': id}
1384                 for f in fields_to_read:
1385                     if id in self.datas:
1386                         r[f] = self.datas[id].get(f, False)
1387                         if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1388                             r[f] = len(r[f])
1389                 result.append(r)
1390                 if id in self.datas:
1391                     self.datas[id]['internal.date_access'] = time.time()
1392             fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1393             for f in fields_post:
1394                 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1395                 for record in result:
1396                     record[f] = res2[record['id']]
1397             if isinstance(ids, (int, long)):
1398                 return result[0]
1399         return result
1400
1401     def write(self, cr, user, ids, vals, context=None):
1402         vals2 = {}
1403         upd_todo = []
1404         for field in vals:
1405             if self._columns[field]._classic_write:
1406                 vals2[field] = vals[field]
1407             else:
1408                 upd_todo.append(field)
1409         for id_new in ids:
1410             self.datas[id_new].update(vals2)
1411             self.datas[id_new]['internal.date_access'] = time.time()
1412             for field in upd_todo:
1413                 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1414         self._validate(cr, user, [id_new], context)
1415         wf_service = netsvc.LocalService("workflow")
1416         wf_service.trg_write(user, self._name, id_new, cr)
1417         self.vaccum(cr, user)
1418         return id_new
1419
1420     def create(self, cr, user, vals, context=None):
1421         self.next_id += 1
1422         id_new = self.next_id
1423         default = []
1424         for f in self._columns.keys():
1425             if not f in vals:
1426                 default.append(f)
1427         if len(default):
1428             vals.update(self.default_get(cr, user, default, context))
1429         vals2 = {}
1430         upd_todo = []
1431         for field in vals:
1432             if self._columns[field]._classic_write:
1433                 vals2[field] = vals[field]
1434             else:
1435                 upd_todo.append(field)
1436         self.datas[id_new] = vals2
1437         self.datas[id_new]['internal.date_access'] = time.time()
1438
1439         for field in upd_todo:
1440             self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1441         self._validate(cr, user, [id_new], context)
1442         wf_service = netsvc.LocalService("workflow")
1443         wf_service.trg_create(user, self._name, id_new, cr)
1444         self.vaccum(cr, user)
1445         return id_new
1446
1447     def default_get(self, cr, uid, fields_list, context=None):
1448         if not context:
1449             context = {}
1450         value = {}
1451         # get the default values for the inherited fields
1452         for f in fields_list:
1453             if f in self._defaults:
1454                 value[f] = self._defaults[f](self, cr, uid, context)
1455             fld_def = ((f in self._columns) and self._columns[f]) \
1456                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1457                     or False
1458
1459         # get the default values set by the user and override the default
1460         # values defined in the object
1461         ir_values_obj = self.pool.get('ir.values')
1462         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1463         for id, field, field_value in res:
1464             if field in fields_list:
1465                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1466                 if fld_def._type in ('many2one', 'one2one'):
1467                     obj = self.pool.get(fld_def._obj)
1468                     if not obj.search(cr, uid, [('id', '=', field_value)]):
1469                         continue
1470                 if fld_def._type in ('many2many'):
1471                     obj = self.pool.get(fld_def._obj)
1472                     field_value2 = []
1473                     for i in range(len(field_value)):
1474                         if not obj.search(cr, uid, [('id', '=',
1475                             field_value[i])]):
1476                             continue
1477                         field_value2.append(field_value[i])
1478                     field_value = field_value2
1479                 if fld_def._type in ('one2many'):
1480                     obj = self.pool.get(fld_def._obj)
1481                     field_value2 = []
1482                     for i in range(len(field_value)):
1483                         field_value2.append({})
1484                         for field2 in field_value[i]:
1485                             if obj._columns[field2]._type in ('many2one', 'one2one'):
1486                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1487                                 if not obj2.search(cr, uid,
1488                                         [('id', '=', field_value[i][field2])]):
1489                                     continue
1490                             # TODO add test for many2many and one2many
1491                             field_value2[i][field2] = field_value[i][field2]
1492                     field_value = field_value2
1493                 value[field] = field_value
1494
1495         # get the default values from the context
1496         for key in context or {}:
1497             if key.startswith('default_'):
1498                 value[key[8:]] = context[key]
1499         return value
1500
1501     def search(self, cr, user, args, offset=0, limit=None, order=None,
1502             context=None, count=False):
1503         return self.datas.keys()
1504
1505     def unlink(self, cr, uid, ids, context=None):
1506         for id in ids:
1507             if id in self.datas:
1508                 del self.datas[id]
1509         if len(ids):
1510             cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str, ids))+')', (self._name, ))
1511         return True
1512
1513     def perm_read(self, cr, user, ids, context=None, details=True):
1514         result = []
1515         for id in ids:
1516             result.append({
1517                 'create_uid': (user, 'Root'),
1518                 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'),
1519                 'write_uid': False,
1520                 'write_date': False,
1521                 'id': id
1522             })
1523         return result
1524
1525     def _check_removed_columns(self, cr, log=False):
1526         # nothing to check in memory...
1527         pass
1528     
1529     def exists(self, cr, uid, id, context=None):
1530         return id in self.datas
1531
1532 class orm(orm_template):
1533     _sql_constraints = []
1534     _table = None
1535     _protected = ['read','write','create','default_get','perm_read','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data','search_count', 'exists']
1536
1537     def _parent_store_compute(self, cr):
1538         logger = netsvc.Logger()
1539         logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
1540         def browse_rec(root, pos=0):
1541 # TODO: set order
1542             where = self._parent_name+'='+str(root)
1543             if not root:
1544                 where = self._parent_name+' IS NULL'
1545             if self._parent_order:
1546                 where += ' order by '+self._parent_order
1547             cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
1548             pos2 = pos + 1
1549             childs = cr.fetchall()
1550             for id in childs:
1551                 pos2 = browse_rec(id[0], pos2)
1552             cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
1553             return pos2+1
1554         query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
1555         if self._parent_order:
1556             query += ' order by '+self._parent_order
1557         pos = 0
1558         cr.execute(query)
1559         for (root,) in cr.fetchall():
1560             pos = browse_rec(root, pos)
1561         return True
1562
1563     def _update_store(self, cr, f, k):
1564         logger = netsvc.Logger()
1565         logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
1566         ss = self._columns[k]._symbol_set
1567         update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
1568         cr.execute('select id from '+self._table)
1569         ids_lst = map(lambda x: x[0], cr.fetchall())
1570         while ids_lst:
1571             iids = ids_lst[:40]
1572             ids_lst = ids_lst[40:]
1573             res = f.get(cr, self, iids, k, 1, {})
1574             for key,val in res.items():
1575                 if f._multi:
1576                     val = val[k]
1577                 # if val is a many2one, just write the ID
1578                 if type(val)==tuple:
1579                     val = val[0]
1580                 if (val<>False) or (type(val)<>bool):
1581                     cr.execute(update_query, (ss[1](val), key))
1582
1583     def _check_removed_columns(self, cr, log=False):
1584         logger = netsvc.Logger()
1585         # iterate on the database columns to drop the NOT NULL constraints
1586         # of fields which were required but have been removed (or will be added by another module)
1587         columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
1588         columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
1589         cr.execute("SELECT a.attname, a.attnotnull"
1590                    "  FROM pg_class c, pg_attribute a"
1591                    " WHERE c.relname=%%s"
1592                    "   AND c.oid=a.attrelid"
1593                    "   AND a.attisdropped=%%s"
1594                    "   AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
1595                    "   AND a.attname NOT IN (%s)" % ",".join(['%s']*len(columns)),
1596                        [self._table, False] + columns)
1597         for column in cr.dictfetchall():
1598             if log:
1599                 logger.notifyChannel("orm", netsvc.LOG_DEBUG, "column %s is in the table %s but not in the corresponding object %s" % (column['attname'], self._table, self._name))
1600             if column['attnotnull']:
1601                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
1602
1603     def _auto_init(self, cr, context={}):
1604         store_compute =  False
1605         logger = netsvc.Logger()
1606         create = False
1607         todo_end = []
1608         self._field_create(cr, context=context)
1609         if not hasattr(self, "_auto") or self._auto:
1610             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
1611             if not cr.rowcount:
1612                 cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table)
1613                 create = True
1614             cr.commit()
1615             if self._parent_store:
1616                 cr.execute("""SELECT c.relname
1617                     FROM pg_class c, pg_attribute a
1618                     WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
1619                     """, (self._table, 'parent_left'))
1620                 if not cr.rowcount:
1621                     if 'parent_left' not in self._columns:
1622                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
1623                     if 'parent_right' not in self._columns:
1624                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
1625                     if self._columns[self._parent_name].ondelete<>'cascade':
1626                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "the columns %s on object must be set as ondelete='cascasde'" % (self._name, self._parent_name))
1627                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
1628                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
1629                     cr.commit()
1630                     store_compute = True
1631
1632             if self._log_access:
1633                 logs = {
1634                     'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1635                     'create_date': 'TIMESTAMP',
1636                     'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1637                     'write_date': 'TIMESTAMP'
1638                 }
1639                 for k in logs:
1640                     cr.execute("""
1641                         SELECT c.relname
1642                           FROM pg_class c, pg_attribute a
1643                          WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
1644                         """, (self._table, k))
1645                     if not cr.rowcount:
1646                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
1647                         cr.commit()
1648
1649             self._check_removed_columns(cr, log=False)
1650
1651             # iterate on the "object columns"
1652             todo_update_store = []
1653             for k in self._columns:
1654                 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
1655                     continue
1656                     #raise _('Can not define a column %s. Reserved keyword !') % (k,)
1657                 f = self._columns[k]
1658
1659                 if isinstance(f, fields.one2many):
1660                     cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
1661                     if cr.fetchone():
1662                         cr.execute("SELECT count(1) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj, f._fields_id))
1663                         res = cr.fetchone()[0]
1664                         if not res:
1665                             cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
1666                 elif isinstance(f, fields.many2many):
1667                     cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
1668                     if not cr.dictfetchall():
1669                         #FIXME: Remove this try/except
1670                         try:
1671                             ref = self.pool.get(f._obj)._table
1672                         except AttributeError:
1673                             ref = f._obj.replace('.', '_')
1674                         cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE) WITH OIDS' % (f._rel, f._id1, self._table, f._id2, ref))
1675                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
1676                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
1677                         cr.commit()
1678                 else:
1679                     cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
1680                                "FROM pg_class c,pg_attribute a,pg_type t " \
1681                                "WHERE c.relname=%s " \
1682                                "AND a.attname=%s " \
1683                                "AND c.oid=a.attrelid " \
1684                                "AND a.atttypid=t.oid", (self._table, k))
1685                     res = cr.dictfetchall()
1686                     if not res:
1687                         if not isinstance(f, fields.function) or f.store:
1688
1689                             # add the missing field
1690                             cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
1691
1692                             # initialize it
1693                             if not create and k in self._defaults:
1694                                 default = self._defaults[k](self, cr, 1, {})
1695                                 ss = self._columns[k]._symbol_set
1696                                 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
1697                                 cr.execute(query, (ss[1](default),))
1698                                 cr.commit()
1699                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
1700                             elif not create:
1701                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
1702
1703                             if isinstance(f, fields.function):
1704                                 order = 10
1705                                 if f.store is not True:
1706                                     order = f.store[f.store.keys()[0]][2]
1707                                 todo_update_store.append((order, f,k))
1708
1709                             # and add constraints if needed
1710                             if isinstance(f, fields.many2one):
1711                                 #FIXME: Remove this try/except
1712                                 try:
1713                                     ref = self.pool.get(f._obj)._table
1714                                 except AttributeError:
1715                                     ref = f._obj.replace('.', '_')
1716                                 # ir_actions is inherited so foreign key doesn't work on it
1717                                 if ref != 'ir_actions':
1718                                     cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
1719                             if f.select:
1720                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
1721                             if f.required:
1722                                 try:
1723                                     cr.commit()
1724                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
1725                                 except Exception, e:
1726                                     logger.notifyChannel('orm', netsvc.LOG_WARNING, 'WARNING: unable to set column %s of table %s not null !\nTry to re-run: openerp-server.py --update=module\nIf it doesn\'t work, update records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
1727                             cr.commit()
1728                     elif len(res)==1:
1729                         f_pg_def = res[0]
1730                         f_pg_type = f_pg_def['typname']
1731                         f_pg_size = f_pg_def['size']
1732                         f_pg_notnull = f_pg_def['attnotnull']
1733                         if isinstance(f, fields.function) and not f.store:
1734                             logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
1735                             cr.execute('ALTER TABLE %s DROP COLUMN %s'% (self._table, k))
1736                             cr.commit()
1737                             f_obj_type = None
1738                         else:
1739                             f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
1740
1741                         if f_obj_type:
1742                             ok = False
1743                             casts = [
1744                                 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
1745                                 ('varchar', 'text', 'TEXT', ''),
1746                                 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
1747                                 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
1748                             ]
1749                             # !!! Avoid reduction of varchar field !!!
1750                             if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
1751                             # if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
1752                                 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
1753                                 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
1754                                 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
1755                                 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
1756                                 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size' % (self._table,))
1757                                 cr.commit()
1758                             for c in casts:
1759                                 if (f_pg_type==c[0]) and (f._type==c[1]):
1760                                     logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
1761                                     ok = True
1762                                     cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
1763                                     cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
1764                                     cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
1765                                     cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
1766                                     cr.commit()
1767
1768                             if f_pg_type != f_obj_type:
1769                                 if not ok:
1770                                     logger.notifyChannel('orm', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed type (DB = %s, def = %s) but unable to migrate this change !" % (k, self._table, f_pg_type, f._type))
1771
1772                             # if the field is required and hasn't got a NOT NULL constraint
1773                             if f.required and f_pg_notnull == 0:
1774                                 # set the field to the default value if any
1775                                 if k in self._defaults:
1776                                     default = self._defaults[k](self, cr, 1, {})
1777                                     if (default is not None):
1778                                         ss = self._columns[k]._symbol_set
1779                                         query = 'UPDATE "%s" SET "%s"=%s WHERE %s is NULL' % (self._table, k, ss[0], k)
1780                                         cr.execute(query, (ss[1](default),))
1781                                 # add the NOT NULL constraint
1782                                 cr.commit()
1783                                 try:
1784                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
1785                                     cr.commit()
1786                                 except Exception, e:
1787                                     logger.notifyChannel('orm', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
1788                                 cr.commit()
1789                             elif not f.required and f_pg_notnull == 1:
1790                                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
1791                                 cr.commit()
1792                             indexname = '%s_%s_index' % (self._table, k)
1793                             cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
1794                             res = cr.dictfetchall()
1795                             if not res and f.select:
1796                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
1797                                 cr.commit()
1798                             if res and not f.select:
1799                                 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
1800                                 cr.commit()
1801                             if isinstance(f, fields.many2one):
1802                                 ref = self.pool.get(f._obj)._table
1803                                 if ref != 'ir_actions':
1804                                     cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
1805                                                 'pg_attribute as att1, pg_attribute as att2 '
1806                                             'WHERE con.conrelid = cl1.oid '
1807                                                 'AND cl1.relname = %s '
1808                                                 'AND con.confrelid = cl2.oid '
1809                                                 'AND cl2.relname = %s '
1810                                                 'AND array_lower(con.conkey, 1) = 1 '
1811                                                 'AND con.conkey[1] = att1.attnum '
1812                                                 'AND att1.attrelid = cl1.oid '
1813                                                 'AND att1.attname = %s '
1814                                                 'AND array_lower(con.confkey, 1) = 1 '
1815                                                 'AND con.confkey[1] = att2.attnum '
1816                                                 'AND att2.attrelid = cl2.oid '
1817                                                 'AND att2.attname = %s '
1818                                                 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
1819                                     res = cr.dictfetchall()
1820                                     if res:
1821                                         confdeltype = {
1822                                             'RESTRICT': 'r',
1823                                             'NO ACTION': 'a',
1824                                             'CASCADE': 'c',
1825                                             'SET NULL': 'n',
1826                                             'SET DEFAULT': 'd',
1827                                         }
1828                                         if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'):
1829                                             cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
1830                                             cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
1831                                             cr.commit()
1832                     else:
1833                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error !")
1834             for order,f,k in todo_update_store:
1835                 todo_end.append((order, self._update_store, (f, k)))
1836
1837         else:
1838             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (self._table,))
1839             create = not bool(cr.fetchone())
1840
1841         for (key, con, _) in self._sql_constraints:
1842             conname = '%s_%s' % (self._table, key)
1843             cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
1844             if not cr.dictfetchall():
1845                 try:
1846                     cr.execute('alter table "%s" add constraint "%s_%s" %s' % (self._table, self._table, key, con,))
1847                     cr.commit()
1848                 except:
1849                     logger.notifyChannel('orm', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table, self._table, key, con,))
1850
1851         if create:
1852             if hasattr(self, "_sql"):
1853                 for line in self._sql.split(';'):
1854                     line2 = line.replace('\n', '').strip()
1855                     if line2:
1856                         cr.execute(line2)
1857                         cr.commit()
1858         if store_compute:
1859             self._parent_store_compute(cr)
1860         return todo_end
1861
1862     def __init__(self, cr):
1863         super(orm, self).__init__(cr)
1864
1865         if not hasattr(self, '_log_access'):
1866             # if not access is not specify, it is the same value as _auto
1867             self._log_access = not hasattr(self, "_auto") or self._auto
1868
1869         self._columns = self._columns.copy()
1870         for store_field in self._columns:
1871             f = self._columns[store_field]
1872             if not isinstance(f, fields.function):
1873                 continue
1874             if not f.store:
1875                 continue
1876             if self._columns[store_field].store is True:
1877                 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10)}
1878             else:
1879                 sm = self._columns[store_field].store
1880             for object, aa in sm.items():
1881                 if len(aa)==3:
1882                     (fnct,fields2,order)=aa
1883                 else:
1884                     raise except_orm('Error',
1885                         ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority)}.' % (store_field, self._name)))
1886                 self.pool._store_function.setdefault(object, [])
1887                 ok = True
1888                 for x,y,z,e,f in self.pool._store_function[object]:
1889                     if (x==self._name) and (y==store_field) and (e==fields2):
1890                         ok = False
1891                 if ok:
1892                     self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order))
1893                     self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
1894
1895         for (key, _, msg) in self._sql_constraints:
1896             self.pool._sql_error[self._table+'_'+key] = msg
1897
1898         # Load manual fields
1899
1900         cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
1901         if cr.fetchone():
1902             cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
1903             for field in cr.dictfetchall():
1904                 if field['name'] in self._columns:
1905                     continue
1906                 attrs = {
1907                     'string': field['field_description'],
1908                     'required': bool(field['required']),
1909                     'readonly': bool(field['readonly']),
1910                     'domain': field['domain'] or None,
1911                     'size': field['size'],
1912                     'ondelete': field['on_delete'],
1913                     'translate': (field['translate']),
1914                     #'select': int(field['select_level'])
1915                 }
1916
1917                 if field['ttype'] == 'selection':
1918                     self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
1919                 elif field['ttype'] == 'reference':
1920                     self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
1921                 elif field['ttype'] == 'many2one':
1922                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
1923                 elif field['ttype'] == 'one2many':
1924                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
1925                 elif field['ttype'] == 'many2many':
1926                     import random
1927                     _rel1 = field['relation'].replace('.', '_')
1928                     _rel2 = field['model'].replace('.', '_')
1929                     _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
1930                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
1931                 else:
1932                     self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
1933
1934         self._inherits_reload()
1935         if not self._sequence:
1936             self._sequence = self._table+'_id_seq'
1937         for k in self._defaults:
1938             assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
1939         for f in self._columns:
1940             self._columns[f].restart()
1941
1942     def default_get(self, cr, uid, fields_list, context=None):
1943         if not context:
1944             context = {}
1945         value = {}
1946         # get the default values for the inherited fields
1947         for t in self._inherits.keys():
1948             value.update(self.pool.get(t).default_get(cr, uid, fields_list,
1949                 context))
1950
1951         # get the default values defined in the object
1952         for f in fields_list:
1953             if f in self._defaults:
1954                 value[f] = self._defaults[f](self, cr, uid, context)
1955             fld_def = ((f in self._columns) and self._columns[f]) \
1956                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1957                     or False
1958             if isinstance(fld_def, fields.property):
1959                 property_obj = self.pool.get('ir.property')
1960                 definition_id = fld_def._field_get(cr, uid, self._name, f)
1961                 nid = property_obj.search(cr, uid, [('fields_id', '=',
1962                     definition_id), ('res_id', '=', False)])
1963                 if nid:
1964                     prop_value = property_obj.browse(cr, uid, nid[0],
1965                             context=context).value
1966                     value[f] = (prop_value and int(prop_value.split(',')[1])) \
1967                             or False
1968
1969         # get the default values set by the user and override the default
1970         # values defined in the object
1971         ir_values_obj = self.pool.get('ir.values')
1972         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1973         for id, field, field_value in res:
1974             if field in fields_list:
1975                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1976                 if fld_def._type in ('many2one', 'one2one'):
1977                     obj = self.pool.get(fld_def._obj)
1978                     if not obj.search(cr, uid, [('id', '=', field_value)]):
1979                         continue
1980                 if fld_def._type in ('many2many'):
1981                     obj = self.pool.get(fld_def._obj)
1982                     field_value2 = []
1983                     for i in range(len(field_value)):
1984                         if not obj.search(cr, uid, [('id', '=',
1985                             field_value[i])]):
1986                             continue
1987                         field_value2.append(field_value[i])
1988                     field_value = field_value2
1989                 if fld_def._type in ('one2many'):
1990                     obj = self.pool.get(fld_def._obj)
1991                     field_value2 = []
1992                     for i in range(len(field_value)):
1993                         field_value2.append({})
1994                         for field2 in field_value[i]:
1995                             if obj._columns[field2]._type in ('many2one', 'one2one'):
1996                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1997                                 if not obj2.search(cr, uid,
1998                                         [('id', '=', field_value[i][field2])]):
1999                                     continue
2000                             # TODO add test for many2many and one2many
2001                             field_value2[i][field2] = field_value[i][field2]
2002                     field_value = field_value2
2003                 value[field] = field_value
2004         for key in context or {}:
2005             if key.startswith('default_'):
2006                 value[key[8:]] = context[key]
2007         return value
2008
2009     #
2010     # Update objects that uses this one to update their _inherits fields
2011     #
2012     def _inherits_reload_src(self):
2013         for obj in self.pool.obj_pool.values():
2014             if self._name in obj._inherits:
2015                 obj._inherits_reload()
2016
2017     def _inherits_reload(self):
2018         res = {}
2019         for table in self._inherits:
2020             res.update(self.pool.get(table)._inherit_fields)
2021             for col in self.pool.get(table)._columns.keys():
2022                 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2023             for col in self.pool.get(table)._inherit_fields.keys():
2024                 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2025         self._inherit_fields = res
2026         self._inherits_reload_src()
2027
2028     def fields_get(self, cr, user, fields=None, context=None):
2029         read_access = self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False)
2030         return super(orm, self).fields_get(cr, user, fields, context, read_access)
2031
2032     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2033         if not context:
2034             context = {}
2035         self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
2036         if not fields:
2037             fields = self._columns.keys() + self._inherit_fields.keys()
2038         select = ids
2039         if isinstance(ids, (int, long)):
2040             select = [ids]
2041         result = self._read_flat(cr, user, select, fields, context, load)
2042         for r in result:
2043             for key, v in r.items():
2044                 if v == None:
2045                     r[key] = False
2046         if isinstance(ids, (int, long)):
2047             return result and result[0] or False
2048         return result
2049
2050     def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2051         if not context:
2052             context = {}
2053         if not ids:
2054             return []
2055
2056         if fields_to_read == None:
2057             fields_to_read = self._columns.keys()
2058
2059         # construct a clause for the rules :
2060         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2061
2062         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2063         fields_pre = [f for f in fields_to_read if
2064                            f == self.CONCURRENCY_CHECK_FIELD
2065                         or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2066                      ] + self._inherits.values()
2067
2068         res = []
2069         if len(fields_pre):
2070             def convert_field(f):
2071                 if f in ('create_date', 'write_date'):
2072                     return "date_trunc('second', %s) as %s" % (f, f)
2073                 if f == self.CONCURRENCY_CHECK_FIELD:
2074                     if self._log_access:
2075                         return "COALESCE(write_date, create_date, now())::timestamp AS %s" % (f,)
2076                     return "now()::timestamp AS %s" % (f,)
2077                 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2078                     return 'length("%s") as "%s"' % (f, f)
2079                 return '"%s"' % (f,)
2080             fields_pre2 = map(convert_field, fields_pre)
2081             for i in range(0, len(ids), cr.IN_MAX):
2082                 sub_ids = ids[i:i+cr.IN_MAX]
2083                 if d1:
2084                     cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \
2085                             (','.join(fields_pre2 + ['id']), self._table,
2086                                 ','.join([str(x) for x in sub_ids]), d1,
2087                                 self._order), d2)
2088                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
2089                         raise except_orm(_('AccessError'),
2090                                 _('You try to bypass an access rule (Document type: %s).') % self._description)
2091                 else:
2092                     cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \
2093                             (','.join(fields_pre2 + ['id']), self._table,
2094                                 ','.join([str(x) for x in sub_ids]),
2095                                 self._order))
2096                 res.extend(cr.dictfetchall())
2097         else:
2098             res = map(lambda x: {'id': x}, ids)
2099
2100         for f in fields_pre:
2101             if f == self.CONCURRENCY_CHECK_FIELD:
2102                 continue
2103             if self._columns[f].translate:
2104                 ids = map(lambda x: x['id'], res)
2105                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2106                 for r in res:
2107                     r[f] = res_trans.get(r['id'], False) or r[f]
2108
2109         for table in self._inherits:
2110             col = self._inherits[table]
2111             cols = intersect(self._inherit_fields.keys(), fields_to_read)
2112             if not cols:
2113                 continue
2114             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
2115
2116             res3 = {}
2117             for r in res2:
2118                 res3[r['id']] = r
2119                 del r['id']
2120
2121             for record in res:
2122                 record.update(res3[record[col]])
2123                 if col not in fields_to_read:
2124                     del record[col]
2125
2126         # all fields which need to be post-processed by a simple function (symbol_get)
2127         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
2128         if fields_post:
2129             # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
2130             # to get the _symbol_get in each occurence
2131             for r in res:
2132                 for f in fields_post:
2133                     r[f] = self._columns[f]._symbol_get(r[f])
2134         ids = map(lambda x: x['id'], res)
2135
2136         # all non inherited fields for which the attribute whose name is in load is False
2137         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2138
2139         # Compute POST fields
2140         todo = {}
2141         for f in fields_post:
2142             todo.setdefault(self._columns[f]._multi, [])
2143             todo[self._columns[f]._multi].append(f)
2144         for key,val in todo.items():
2145             if key:
2146                 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2147                 for pos in val:
2148                     for record in res:
2149                         record[pos] = res2[record['id']][pos]
2150             else:
2151                 for f in val:
2152                     res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2153                     for record in res:
2154                         record[f] = res2[record['id']]
2155
2156 #for f in fields_post:
2157 #    # get the value of that field for all records/ids
2158 #    res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2159 #    for record in res:
2160 #        record[f] = res2[record['id']]
2161
2162         readonly = None
2163         for vals in res:
2164             for field in vals.copy():
2165                 fobj = None
2166                 if field in self._columns:
2167                     fobj = self._columns[field]
2168
2169                 if not fobj:
2170                     continue
2171                 groups = fobj.read
2172                 if groups:
2173                     edit = False
2174                     for group in groups:
2175                         module = group.split(".")[0]
2176                         grp = group.split(".")[1]
2177                         cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \
2178                                    (grp, module, 'res.groups', user))
2179                         readonly = cr.fetchall()
2180                         if readonly[0][0] >= 1:
2181                             edit = True
2182                             break
2183                         elif readonly[0][0] == 0:
2184                             edit = False
2185                         else:
2186                             edit = False
2187
2188                     if not edit:
2189                         if type(vals[field]) == type([]):
2190                             vals[field] = []
2191                         elif type(vals[field]) == type(0.0):
2192                             vals[field] = 0
2193                         elif type(vals[field]) == type(''):
2194                             vals[field] = '=No Permission='
2195                         else:
2196                             vals[field] = False
2197         return res
2198
2199     def perm_read(self, cr, user, ids, context=None, details=True):
2200         if not context:
2201             context = {}
2202         if not ids:
2203             return []
2204         fields = ''
2205         if self._log_access:
2206             fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date'
2207         if isinstance(ids, (int, long)):
2208             ids_str = str(ids)
2209         else:
2210             ids_str = string.join(map(lambda x: str(x), ids), ',')
2211         cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
2212         res = cr.dictfetchall()
2213         for r in res:
2214             for key in r:
2215                 r[key] = r[key] or False
2216                 if key in ('write_uid', 'create_uid', 'uid') and details:
2217                     if r[key]:
2218                         r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
2219         if isinstance(ids, (int, long)):
2220             return res[ids]
2221         return res
2222
2223     def _check_concurrency(self, cr, ids, context):
2224         if not context:
2225             return
2226         if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
2227             def key(oid):
2228                 return "%s,%s" % (self._name, oid)
2229             santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
2230             for i in range(0, len(ids), cr.IN_MAX):
2231                 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
2232                                           for oid in ids[i:i+cr.IN_MAX]
2233                                           if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
2234                 if sub_ids:
2235                     cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
2236                     res = cr.fetchone()
2237                     if res and res[0]:
2238                         raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
2239
2240     def unlink(self, cr, uid, ids, context=None):
2241         if not ids:
2242             return True
2243         if isinstance(ids, (int, long)):
2244             ids = [ids]
2245
2246         result_store = self._store_get_values(cr, uid, ids, None, context)
2247
2248         self._check_concurrency(cr, ids, context)
2249
2250         self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink')
2251
2252         wf_service = netsvc.LocalService("workflow")
2253         for id in ids:
2254             wf_service.trg_delete(uid, self._name, id, cr)
2255
2256         #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
2257         #res = cr.dictfetchall()
2258         #for key in self._inherits:
2259         #   ids2 = [x[self._inherits[key]] for x in res]
2260         #   self.pool.get(key).unlink(cr, uid, ids2)
2261
2262         d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name)
2263         if d1:
2264             d1 = ' AND '+d1
2265
2266         for i in range(0, len(ids), cr.IN_MAX):
2267             sub_ids = ids[i:i+cr.IN_MAX]
2268             str_d = string.join(('%s',)*len(sub_ids), ',')
2269             if d1:
2270                 cr.execute('SELECT id FROM "'+self._table+'" ' \
2271                         'WHERE id IN ('+str_d+')'+d1, sub_ids+d2)
2272                 if not cr.rowcount == len({}.fromkeys(ids)):
2273                     raise except_orm(_('AccessError'),
2274                             _('You try to bypass an access rule (Document type: %s).') % \
2275                                     self._description)
2276
2277             if d1:
2278                 cr.execute('delete from "'+self._table+'" ' \
2279                         'where id in ('+str_d+')'+d1, sub_ids+d2)
2280             else:
2281                 cr.execute('delete from "'+self._table+'" ' \
2282                         'where id in ('+str_d+')', sub_ids)
2283
2284         for order, object, ids, fields in result_store:
2285             if object<>self._name:
2286                 obj =  self.pool.get(object)
2287                 cr.execute('select id from '+obj._table+' where id in ('+','.join(map(str, ids))+')')
2288                 ids = map(lambda x: x[0], cr.fetchall())
2289                 if ids:
2290                     obj._store_set_values(cr, uid, ids, fields, context)
2291         return True
2292
2293     #
2294     # TODO: Validate
2295     #
2296     def write(self, cr, user, ids, vals, context=None):
2297         readonly = None
2298         for field in vals.copy():
2299             fobj = None
2300             if field in self._columns:
2301                 fobj = self._columns[field]
2302             else:
2303                 fobj = self._inherit_fields[field][2]
2304             if not fobj:
2305                 continue
2306             groups = fobj.write
2307
2308             if groups:
2309                 edit = False
2310                 for group in groups:
2311                     module = group.split(".")[0]
2312                     grp = group.split(".")[1]
2313                     cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \
2314                                (grp, module, 'res.groups', user))
2315                     readonly = cr.fetchall()
2316                     if readonly[0][0] >= 1:
2317                         edit = True
2318                         break
2319                     elif readonly[0][0] == 0:
2320                         edit = False
2321                     else:
2322                         edit = False
2323
2324                 if not edit:
2325                     vals.pop(field)
2326
2327         if not context:
2328             context = {}
2329         if not ids:
2330             return True
2331         if isinstance(ids, (int, long)):
2332             ids = [ids]
2333
2334         self._check_concurrency(cr, ids, context)
2335
2336         self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
2337
2338         upd0 = []
2339         upd1 = []
2340         upd_todo = []
2341         updend = []
2342         direct = []
2343         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
2344         for field in vals:
2345             if field in self._columns:
2346                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
2347                     if (not totranslate) or not self._columns[field].translate:
2348                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
2349                         upd1.append(self._columns[field]._symbol_set[1](vals[field]))
2350                     direct.append(field)
2351                 else:
2352                     upd_todo.append(field)
2353             else:
2354                 updend.append(field)
2355             if field in self._columns \
2356                     and hasattr(self._columns[field], 'selection') \
2357                     and vals[field]:
2358                 if self._columns[field]._type == 'reference':
2359                     val = vals[field].split(',')[0]
2360                 else:
2361                     val = vals[field]
2362                 if isinstance(self._columns[field].selection, (tuple, list)):
2363                     if val not in dict(self._columns[field].selection):
2364                         raise except_orm(_('ValidateError'),
2365                         _('The value "%s" for the field "%s" is not in the selection') \
2366                                 % (vals[field], field))
2367                 else:
2368                     if val not in dict(self._columns[field].selection(
2369                         self, cr, user, context=context)):
2370                         raise except_orm(_('ValidateError'),
2371                         _('The value "%s" for the field "%s" is not in the selection') \
2372                                 % (vals[field], field))
2373
2374         if self._log_access:
2375             upd0.append('write_uid=%s')
2376             upd0.append('write_date=now()')
2377             upd1.append(user)
2378
2379         if len(upd0):
2380
2381             d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2382             if d1:
2383                 d1 = ' and '+d1
2384
2385             for i in range(0, len(ids), cr.IN_MAX):
2386                 sub_ids = ids[i:i+cr.IN_MAX]
2387                 ids_str = string.join(map(str, sub_ids), ',')
2388                 if d1:
2389                     cr.execute('SELECT id FROM "'+self._table+'" ' \
2390                             'WHERE id IN ('+ids_str+')'+d1, d2)
2391                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
2392                         raise except_orm(_('AccessError'),
2393                                 _('You try to bypass an access rule (Document type: %s).') % \
2394                                         self._description)
2395                 else:
2396                     cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')')
2397                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
2398                         raise except_orm(_('AccessError'),
2399                                 _('You try to write on an record that doesn\'t exist ' \
2400                                         '(Document type: %s).') % self._description)
2401                 if d1:
2402                     cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \
2403                             'where id in ('+ids_str+')'+d1, upd1+ d2)
2404                 else:
2405                     cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \
2406                             'where id in ('+ids_str+')', upd1)
2407             
2408             if totranslate:
2409                 for f in direct:
2410                     if self._columns[f].translate:
2411                         src_trans = self.pool.get(self._name).read(cr,user,ids,[f])
2412                         self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans[0][f])
2413
2414         # call the 'set' method of fields which are not classic_write
2415         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
2416
2417         # default element in context must be remove when call a one2many or many2many
2418         rel_context = context.copy()
2419         for c in context.items():
2420             if c[0].startswith('default_'):
2421                 del rel_context[c[0]]
2422
2423         for field in upd_todo:
2424             for id in ids:
2425                 self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context)
2426
2427         for table in self._inherits:
2428             col = self._inherits[table]
2429             nids = []
2430             for i in range(0, len(ids), cr.IN_MAX):
2431                 sub_ids = ids[i:i+cr.IN_MAX]
2432                 ids_str = string.join(map(str, sub_ids), ',')
2433                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2434                         'where id in ('+ids_str+')', upd1)
2435                 nids.extend([x[0] for x in cr.fetchall()])
2436
2437             v = {}
2438             for val in updend:
2439                 if self._inherit_fields[val][0] == table:
2440                     v[val] = vals[val]
2441             self.pool.get(table).write(cr, user, nids, v, context)
2442
2443         self._validate(cr, user, ids, context)
2444 # TODO: use _order to set dest at the right position and not first node of parent
2445         if self._parent_store and (self._parent_name in vals):
2446             if self.pool._init:
2447                 self.pool._init_parent[self._name]=True
2448             else:
2449                 for id in ids:
2450                     # Find Position of the element
2451                     if vals[self._parent_name]:
2452                         cr.execute('select parent_left,parent_right,id from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (vals[self._parent_name],))
2453                     else:
2454                         cr.execute('select parent_left,parent_right,id from '+self._table+' where '+self._parent_name+' is null order by '+(self._parent_order or self._order))
2455                     result_p = cr.fetchall()
2456                     position = None
2457                     for (pleft,pright,pid) in result_p:
2458                         if pid == id:
2459                             break
2460                         position = pright+1
2461
2462                     # It's the first node of the parent: position = parent_left+1
2463                     if not position:
2464                         if not vals[self._parent_name]:
2465                             position = 1
2466                         else:
2467                             cr.execute('select parent_left from '+self._table+' where id=%s', (vals[self._parent_name],))
2468                             position = cr.fetchone()[0]+1
2469
2470                     # We have the new position !
2471                     cr.execute('select parent_left,parent_right from '+self._table+' where id=%s', (id,))
2472                     pleft,pright = cr.fetchone()
2473                     distance = pright - pleft + 1
2474
2475                     if position>pleft and position<=pright:
2476                         raise except_orm(_('UserError'), _('Recursivity Detected.'))
2477
2478                     if pleft<position:
2479                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2480                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2481                         cr.execute('update '+self._table+' set parent_left=parent_left+%s, parent_right=parent_right+%s where parent_left>=%s and parent_left<%s', (position-pleft,position-pleft, pleft, pright))
2482                     else:
2483                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2484                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2485                         cr.execute('update '+self._table+' set parent_left=parent_left-%s, parent_right=parent_right-%s where parent_left>=%s and parent_left<%s', (pleft-position+distance,pleft-position+distance, pleft+distance, pright+distance))
2486
2487         result = self._store_get_values(cr, user, ids, vals.keys(), context)
2488         for order, object, ids, fields in result:
2489             self.pool.get(object)._store_set_values(cr, user, ids, fields, context)
2490
2491         wf_service = netsvc.LocalService("workflow")
2492         for id in ids:
2493             wf_service.trg_write(user, self._name, id, cr)
2494         return True
2495
2496     #
2497     # TODO: Should set perm to user.xxx
2498     #
2499     def create(self, cr, user, vals, context=None):
2500         """ create(cr, user, vals, context) -> int
2501         cr = database cursor
2502         user = user id
2503         vals = dictionary of the form {'field_name':field_value, ...}
2504         """
2505         if not context:
2506             context = {}
2507         self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
2508
2509         default = []
2510
2511         avoid_table = []
2512         for (t, c) in self._inherits.items():
2513             if c in vals:
2514                 avoid_table.append(t)
2515         for f in self._columns.keys(): # + self._inherit_fields.keys():
2516             if not f in vals:
2517                 default.append(f)
2518
2519         for f in self._inherit_fields.keys():
2520             if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
2521                 default.append(f)
2522
2523         if len(default):
2524             default_values = self.default_get(cr, user, default, context)
2525             for dv in default_values:
2526                 if dv in self._columns and self._columns[dv]._type == 'many2many':
2527                     if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
2528                         default_values[dv] = [(6, 0, default_values[dv])]
2529
2530             vals.update(default_values)
2531
2532         tocreate = {}
2533         for v in self._inherits:
2534             if self._inherits[v] not in vals:
2535                 tocreate[v] = {}
2536
2537         (upd0, upd1, upd2) = ('', '', [])
2538         upd_todo = []
2539
2540         for v in vals.keys():
2541             if v in self._inherit_fields:
2542                 (table, col, col_detail) = self._inherit_fields[v]
2543                 tocreate[table][v] = vals[v]
2544                 del vals[v]
2545
2546         # Try-except added to filter the creation of those records whose filds are readonly.
2547         # Example : any dashboard which has all the fields readonly.(due to Views(database views))
2548         try:
2549             cr.execute("SELECT nextval('"+self._sequence+"')")
2550         except:
2551             raise except_orm(_('UserError'),
2552                         _('You cannot perform this operation.'))
2553
2554         id_new = cr.fetchone()[0]
2555         for table in tocreate:
2556             id = self.pool.get(table).create(cr, user, tocreate[table])
2557             upd0 += ','+self._inherits[table]
2558             upd1 += ',%s'
2559             upd2.append(id)
2560         
2561         #Start : Set bool fields to be False if they are not touched(to make search more powerful) 
2562         bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
2563         
2564         for bool_field in bool_fields:
2565             if bool_field not in vals:
2566                 vals[bool_field] = False
2567         #End
2568         
2569         for field in vals:
2570             if self._columns[field]._classic_write:
2571                 upd0 = upd0 + ',"' + field + '"'
2572                 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
2573                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
2574             else:
2575                 upd_todo.append(field)
2576             if field in self._columns \
2577                     and hasattr(self._columns[field], 'selection') \
2578                     and vals[field]:
2579                 if self._columns[field]._type == 'reference':
2580                     val = vals[field].split(',')[0]
2581                 else:
2582                     val = vals[field]
2583                 if isinstance(self._columns[field].selection, (tuple, list)):
2584                     if val not in dict(self._columns[field].selection):
2585                         raise except_orm(_('ValidateError'),
2586                         _('The value "%s" for the field "%s" is not in the selection') \
2587                                 % (vals[field], field))
2588                 else:
2589                     if val not in dict(self._columns[field].selection(
2590                         self, cr, user, context=context)):
2591                         raise except_orm(_('ValidateError'),
2592                         _('The value "%s" for the field "%s" is not in the selection') \
2593                                 % (vals[field], field))
2594         if self._log_access:
2595             upd0 += ',create_uid,create_date'
2596             upd1 += ',%s,now()'
2597             upd2.append(user)
2598         cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
2599         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
2600
2601         if self._parent_store:
2602             if self.pool._init:
2603                 self.pool._init_parent[self._name]=True
2604             else:
2605                 parent = vals.get(self._parent_name, False)
2606                 if parent:
2607                     cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
2608                     pleft_old = None
2609                     result_p = cr.fetchall()
2610                     for (pleft,) in result_p:
2611                         if not pleft:
2612                             break
2613                         pleft_old = pleft
2614                     if not pleft_old:
2615                         cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
2616                         pleft_old = cr.fetchone()[0]
2617                     pleft = pleft_old
2618                 else:
2619                     cr.execute('select max(parent_right) from '+self._table)
2620                     pleft = cr.fetchone()[0] or 0
2621                 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
2622                 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
2623                 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
2624                 
2625         # default element in context must be remove when call a one2many or many2many
2626         rel_context = context.copy()
2627         for c in context.items():
2628             if c[0].startswith('default_'):
2629                 del rel_context[c[0]]
2630
2631         for field in upd_todo:
2632             self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context)
2633         self._validate(cr, user, [id_new], context)
2634
2635         result = self._store_get_values(cr, user, [id_new], vals.keys(), context)
2636         for order, object, ids, fields in result:
2637             self.pool.get(object)._store_set_values(cr, user, ids, fields, context)
2638
2639         wf_service = netsvc.LocalService("workflow")
2640         wf_service.trg_create(user, self._name, id_new, cr)
2641         return id_new
2642
2643     def _store_get_values(self, cr, uid, ids, fields, context):
2644         result = {}
2645         fncts = self.pool._store_function.get(self._name, [])
2646         for fnct in range(len(fncts)):
2647             result.setdefault(fncts[fnct][0], {})
2648             ids2 = fncts[fnct][2](self,cr, uid, ids, context)
2649             for id in filter(None, ids2):
2650                 result[fncts[fnct][0]].setdefault(id, [])
2651                 result[fncts[fnct][0]][id].append(fnct)
2652         dict = {}
2653         for object in result:
2654             k2 = {}
2655             for id,fnct in result[object].items():
2656                 k2.setdefault(tuple(fnct), [])
2657                 k2[tuple(fnct)].append(id)
2658             for fnct,id in k2.items():
2659                 dict.setdefault(fncts[fnct[0]][4],[])
2660                 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
2661         result2 = []
2662         tmp = dict.keys()
2663         tmp.sort()
2664         for k in tmp:
2665             result2+=dict[k]
2666         return result2
2667
2668     def _store_set_values(self, cr, uid, ids, fields, context):
2669         todo = {}
2670         keys = []
2671         for f in fields:
2672             if self._columns[f]._multi not in keys:
2673                 keys.append(self._columns[f]._multi)
2674             todo.setdefault(self._columns[f]._multi, [])
2675             todo[self._columns[f]._multi].append(f)
2676         for key in keys:
2677             val = todo[key]
2678             if key:
2679                 result = self._columns[val[0]].get(cr, self, ids, val, uid, context=context)
2680                 for id,value in result.items():
2681                     upd0 = []
2682                     upd1 = []
2683                     for v in value:
2684                         if v not in val:
2685                             continue
2686                         if self._columns[v]._type in ('many2one', 'one2one'):
2687                             try:
2688                                 value[v] = value[v][0]
2689                             except:
2690                                 pass
2691                         upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
2692                         upd1.append(self._columns[v]._symbol_set[1](value[v]))
2693                     upd1.append(id)
2694                     cr.execute('update "' + self._table + '" set ' + \
2695                         string.join(upd0, ',') + ' where id = %s', upd1)
2696
2697             else:
2698                 for f in val:
2699                     result = self._columns[f].get(cr, self, ids, f, uid, context=context)
2700                     for id,value in result.items():
2701                         if self._columns[f]._type in ('many2one', 'one2one'):
2702                             try:
2703                                 value = value[0]
2704                             except:
2705                                 pass
2706                         cr.execute('update "' + self._table + '" set ' + \
2707                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
2708         return True
2709
2710     #
2711     # TODO: Validate
2712     #
2713     def perm_write(self, cr, user, ids, fields, context=None):
2714         raise _('This method does not exist anymore')
2715
2716     # TODO: ameliorer avec NULL
2717     def _where_calc(self, cr, user, args, active_test=True, context=None):
2718         if not context:
2719             context = {}
2720         args = args[:]
2721         # if the object has a field named 'active', filter out all inactive
2722         # records unless they were explicitely asked for
2723         if 'active' in self._columns and (active_test and context.get('active_test', True)):
2724             if args:
2725                 active_in_args = False
2726                 for a in args:
2727                     if a[0] == 'active':
2728                         active_in_args = True
2729                 if not active_in_args:
2730                     args.insert(0, ('active', '=', 1))
2731             else:
2732                 args = [('active', '=', 1)]
2733
2734         if args:
2735             import expression
2736             e = expression.expression(args)
2737             e.parse(cr, user, self, context)
2738             tables = e.get_tables()
2739             qu1, qu2 = e.to_sql()
2740             qu1 = qu1 and [qu1] or []
2741         else:
2742             qu1, qu2, tables = [], [], ['"%s"' % self._table]
2743
2744         return (qu1, qu2, tables)
2745
2746     def _check_qorder(self, word):
2747         if not regex_order.match(word):
2748             raise except_orm(_('AccessError'), _('Bad query.'))
2749         return True
2750
2751     def search(self, cr, user, args, offset=0, limit=None, order=None,
2752             context=None, count=False):
2753         if not context:
2754             context = {}
2755         # compute the where, order by, limit and offset clauses
2756         (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
2757
2758         if len(qu1):
2759             qu1 = ' where '+string.join(qu1, ' and ')
2760         else:
2761             qu1 = ''
2762
2763         if order:
2764             self._check_qorder(order)
2765         order_by = order or self._order
2766
2767         limit_str = limit and ' limit %d' % limit or ''
2768         offset_str = offset and ' offset %d' % offset or ''
2769
2770
2771         # construct a clause for the rules :
2772         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2773         if d1:
2774             qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
2775             qu2 += d2
2776
2777         if count:
2778             cr.execute('select count(%s.id) from ' % self._table +
2779                     ','.join(tables) +qu1 + limit_str + offset_str, qu2)
2780             res = cr.fetchall()
2781             return res[0][0]
2782         # execute the "main" query to fetch the ids we were searching for
2783         cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
2784         res = cr.fetchall()
2785         return [x[0] for x in res]
2786
2787     # returns the different values ever entered for one field
2788     # this is used, for example, in the client when the user hits enter on
2789     # a char field
2790     def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
2791         if not args:
2792             args = []
2793         if field in self._inherit_fields:
2794             return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
2795         else:
2796             return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
2797
2798     def name_get(self, cr, user, ids, context=None):
2799         if not context:
2800             context = {}
2801         if not ids:
2802             return []
2803         if isinstance(ids, (int, long)):
2804             ids = [ids]
2805         return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
2806             [self._rec_name], context, load='_classic_write')]
2807
2808     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=None):
2809         if not args:
2810             args = []
2811         if not context:
2812             context = {}
2813         args = args[:]
2814         if name:
2815             args += [(self._rec_name, operator, name)]
2816         ids = self.search(cr, user, args, limit=limit, context=context)
2817         res = self.name_get(cr, user, ids, context)
2818         return res
2819
2820     def copy_data(self, cr, uid, id, default=None, context=None):
2821         if not context:
2822             context = {}
2823         if not default:
2824             default = {}
2825         if 'state' not in default:
2826             if 'state' in self._defaults:
2827                 default['state'] = self._defaults['state'](self, cr, uid, context)
2828         data = self.read(cr, uid, [id], context=context)[0]
2829         fields = self.fields_get(cr, uid)
2830         trans_data=[]
2831         for f in fields:
2832             ftype = fields[f]['type']
2833
2834             if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
2835                 del data[f]
2836
2837             if f in default:
2838                 data[f] = default[f]
2839             elif ftype == 'function':
2840                 del data[f]
2841             elif ftype == 'many2one':
2842                 try:
2843                     data[f] = data[f] and data[f][0]
2844                 except:
2845                     pass
2846             elif ftype in ('one2many', 'one2one'):
2847                 res = []
2848                 rel = self.pool.get(fields[f]['relation'])
2849                 for rel_id in data[f]:
2850                     # the lines are first duplicated using the wrong (old)
2851                     # parent but then are reassigned to the correct one thanks
2852                     # to the (4, ...)
2853                     d,t = rel.copy_data(cr, uid, rel_id, context=context)
2854                     res.append((0, 0, d))
2855                     trans_data += t
2856                 data[f] = res
2857             elif ftype == 'many2many':
2858                 data[f] = [(6, 0, data[f])]
2859
2860         trans_obj = self.pool.get('ir.translation')
2861         trans_name=''
2862         for f in fields:
2863             trans_flag=True
2864             if f in self._columns and self._columns[f].translate:
2865                 trans_name=self._name+","+f
2866             elif f in self._inherit_fields and self._inherit_fields[f][2].translate:
2867                 trans_name=self._inherit_fields[f][0]+","+f
2868             else:
2869                 trans_flag=False
2870
2871             if trans_flag:
2872                 trans_ids = trans_obj.search(cr, uid, [
2873                         ('name', '=', trans_name),
2874                         ('res_id','=',data['id'])
2875                     ])
2876
2877                 trans_data.extend(trans_obj.read(cr,uid,trans_ids,context=context))
2878
2879         del data['id']
2880
2881         for v in self._inherits:
2882             del data[self._inherits[v]]
2883         return data, trans_data
2884
2885     def copy(self, cr, uid, id, default=None, context=None):
2886         trans_obj = self.pool.get('ir.translation')
2887         data, trans_data = self.copy_data(cr, uid, id, default, context)
2888         new_id=self.create(cr, uid, data)
2889         for record in trans_data:
2890             del record['id']
2891             record['res_id']=new_id
2892             trans_obj.create(cr,uid,record)
2893         return new_id
2894
2895     def exists(self, cr, uid, id, context=None):
2896         cr.execute('SELECT count(1) FROM "%s" where id=%%s' % (self._table,), (id,))
2897         return bool(cr.fetchone()[0])
2898
2899     def check_recursion(self, cr, uid, ids, parent=None):
2900         if not parent:
2901             parent = self._parent_name
2902         ids_parent = ids[:]
2903         while len(ids_parent):
2904             ids_parent2 = []
2905             for i in range(0, len(ids), cr.IN_MAX):
2906                 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
2907                 cr.execute('SELECT distinct "'+parent+'"'+
2908                     ' FROM "'+self._table+'" ' \
2909                     'WHERE id in ('+','.join(map(str, sub_ids_parent))+')')
2910                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
2911             ids_parent = ids_parent2
2912             for i in ids_parent:
2913                 if i in ids:
2914                     return False
2915         return True
2916
2917
2918 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2919