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