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