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