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