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