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