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