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