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