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