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