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