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