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