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