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