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