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