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