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