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