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