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