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