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