[FIX] orm, workflow: do not disable wkf buttons unless explicit group is set on trans...
[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                          AND t.group_id is NOT NULL
1386                    """, (self._name, button.get('name')))
1387             group_ids = [x[0] for x in cr.fetchall() if x[0]]
1388             can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1389             button.set('readonly', str(int(not can_click)))
1390         return node
1391
1392     def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1393         fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1394         node = self._disable_workflow_buttons(cr, user, node)
1395         arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1396         fields = {}
1397         if node.tag == 'diagram':
1398             if node.getchildren()[0].tag == 'node':
1399                 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1400             if node.getchildren()[1].tag == 'arrow':
1401                 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1402             for key, value in node_fields.items():
1403                 fields[key] = value
1404             for key, value in arrow_fields.items():
1405                 fields[key] = value
1406         else:
1407             fields = self.fields_get(cr, user, fields_def.keys(), context)
1408         for field in fields_def:
1409             if field == 'id':
1410                 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1411                 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1412             elif field in fields:
1413                 fields[field].update(fields_def[field])
1414             else:
1415                 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))
1416                 res = cr.fetchall()[:]
1417                 model = res[0][1]
1418                 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1419                 msg = "\n * ".join([r[0] for r in res])
1420                 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1421                 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1422                 raise except_orm('View error', msg)
1423         return arch, fields
1424
1425     def __get_default_calendar_view(self):
1426         """Generate a default calendar view (For internal use only).
1427         """
1428
1429         arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1430                 '<calendar string="%s"') % (self._description)
1431
1432         if (self._date_name not in self._columns):
1433             date_found = False
1434             for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1435                 if dt in self._columns:
1436                     self._date_name = dt
1437                     date_found = True
1438                     break
1439
1440             if not date_found:
1441                 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1442
1443         if self._date_name:
1444             arch += ' date_start="%s"' % (self._date_name)
1445
1446         for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1447             if color in self._columns:
1448                 arch += ' color="' + color + '"'
1449                 break
1450
1451         dt_stop_flag = False
1452
1453         for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1454             if dt_stop in self._columns:
1455                 arch += ' date_stop="' + dt_stop + '"'
1456                 dt_stop_flag = True
1457                 break
1458
1459         if not dt_stop_flag:
1460             for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1461                 if dt_delay in self._columns:
1462                     arch += ' date_delay="' + dt_delay + '"'
1463                     break
1464
1465         arch += ('>\n'
1466                  '  <field name="%s"/>\n'
1467                  '</calendar>') % (self._rec_name)
1468
1469         return arch
1470
1471     def __get_default_search_view(self, cr, uid, context={}):
1472
1473         def encode(s):
1474             if isinstance(s, unicode):
1475                 return s.encode('utf8')
1476             return s
1477
1478         view = self.fields_view_get(cr, uid, False, 'form', context)
1479
1480         root = etree.fromstring(encode(view['arch']))
1481         res = etree.XML("""<search string="%s"></search>""" % root.get("string", ""))
1482         node = etree.Element("group")
1483         res.append(node)
1484
1485         fields = root.xpath("//field[@select=1]")
1486         for field in fields:
1487             node.append(field)
1488
1489         return etree.tostring(res, encoding="utf-8").replace('\t', '')
1490
1491     #
1492     # if view_id, view_type is not required
1493     #
1494     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1495         """
1496         Get the detailed composition of the requested view like fields, model, view architecture
1497
1498         :param cr: database cursor
1499         :param user: current user id
1500         :param view_id: id of the view or None
1501         :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1502         :param context: context arguments, like lang, time zone
1503         :param toolbar: true to include contextual actions
1504         :param submenu: example (portal_project module)
1505         :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1506         :raise AttributeError:
1507                             * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1508                             * if some tag other than 'position' is found in parent view
1509         :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1510
1511         """
1512         if not context:
1513             context = {}
1514
1515         def encode(s):
1516             if isinstance(s, unicode):
1517                 return s.encode('utf8')
1518             return s
1519
1520         def _inherit_apply(src, inherit):
1521             def _find(node, node2):
1522                 if node2.tag == 'xpath':
1523                     res = node.xpath(node2.get('expr'))
1524                     if res:
1525                         return res[0]
1526                     else:
1527                         return None
1528                 else:
1529                     for n in node.getiterator(node2.tag):
1530                         res = True
1531                         if node2.tag == 'field':
1532                             # only compare field names, a field can be only once in a given view
1533                             # at a given level (and for multilevel expressions, we should use xpath
1534                             # inheritance spec anyway)
1535                             if node2.get('name') == n.get('name'):
1536                                 return n
1537                             else:
1538                                 continue
1539                         for attr in node2.attrib:
1540                             if attr == 'position':
1541                                 continue
1542                             if n.get(attr):
1543                                 if n.get(attr) == node2.get(attr):
1544                                     continue
1545                             res = False
1546                         if res:
1547                             return n
1548                 return None
1549
1550             # End: _find(node, node2)
1551
1552             doc_dest = etree.fromstring(encode(inherit))
1553             toparse = [doc_dest]
1554
1555             while len(toparse):
1556                 node2 = toparse.pop(0)
1557                 if node2.tag == 'data':
1558                     toparse += [ c for c in doc_dest ]
1559                     continue
1560                 node = _find(src, node2)
1561                 if node is not None:
1562                     pos = 'inside'
1563                     if node2.get('position'):
1564                         pos = node2.get('position')
1565                     if pos == 'replace':
1566                         parent = node.getparent()
1567                         if parent is None:
1568                             src = copy.deepcopy(node2[0])
1569                         else:
1570                             for child in node2:
1571                                 node.addprevious(child)
1572                             node.getparent().remove(node)
1573                     elif pos == 'attributes':
1574                         for child in node2.getiterator('attribute'):
1575                             attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1576                             if attribute[1]:
1577                                 node.set(attribute[0], attribute[1])
1578                             else:
1579                                 del(node.attrib[attribute[0]])
1580                     else:
1581                         sib = node.getnext()
1582                         for child in node2:
1583                             if pos == 'inside':
1584                                 node.append(child)
1585                             elif pos == 'after':
1586                                 if sib is None:
1587                                     node.addnext(child)
1588                                 else:
1589                                     sib.addprevious(child)
1590                             elif pos == 'before':
1591                                 node.addprevious(child)
1592                             else:
1593                                 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1594                 else:
1595                     attrs = ''.join([
1596                         ' %s="%s"' % (attr, node2.get(attr))
1597                         for attr in node2.attrib
1598                         if attr != 'position'
1599                     ])
1600                     tag = "<%s%s>" % (node2.tag, attrs)
1601                     raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1602             return src
1603         # End: _inherit_apply(src, inherit)
1604
1605         result = {'type': view_type, 'model': self._name}
1606
1607         ok = True
1608         model = True
1609         sql_res = False
1610         while ok:
1611             view_ref = context.get(view_type + '_view_ref', False)
1612             if view_ref and not view_id:
1613                 if '.' in view_ref:
1614                     module, view_ref = view_ref.split('.', 1)
1615                     cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1616                     view_ref_res = cr.fetchone()
1617                     if view_ref_res:
1618                         view_id = view_ref_res[0]
1619
1620             if view_id:
1621                 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1622                 params = (view_id,)
1623                 if model:
1624                     query += " AND model=%s"
1625                     params += (self._name,)
1626                 cr.execute(query, params)
1627             else:
1628                 cr.execute('''SELECT
1629                         arch,name,field_parent,id,type,inherit_id
1630                     FROM
1631                         ir_ui_view
1632                     WHERE
1633                         model=%s AND
1634                         type=%s AND
1635                         inherit_id IS NULL
1636                     ORDER BY priority''', (self._name, view_type))
1637             sql_res = cr.fetchone()
1638
1639             if not sql_res:
1640                 break
1641
1642             ok = sql_res[5]
1643             view_id = ok or sql_res[3]
1644             model = False
1645
1646         # if a view was found
1647         if sql_res:
1648             result['type'] = sql_res[4]
1649             result['view_id'] = sql_res[3]
1650             result['arch'] = sql_res[0]
1651
1652             def _inherit_apply_rec(result, inherit_id):
1653                 # get all views which inherit from (ie modify) this view
1654                 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1655                 sql_inherit = cr.fetchall()
1656                 for (inherit, id) in sql_inherit:
1657                     result = _inherit_apply(result, inherit)
1658                     result = _inherit_apply_rec(result, id)
1659                 return result
1660
1661             inherit_result = etree.fromstring(encode(result['arch']))
1662             result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1663
1664             result['name'] = sql_res[1]
1665             result['field_parent'] = sql_res[2] or False
1666         else:
1667
1668             # otherwise, build some kind of default view
1669             if view_type == 'form':
1670                 res = self.fields_get(cr, user, context=context)
1671                 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1672                      '<form string="%s">' % (self._description,)
1673                 for x in res:
1674                     if res[x]['type'] not in ('one2many', 'many2many'):
1675                         xml += '<field name="%s"/>' % (x,)
1676                         if res[x]['type'] == 'text':
1677                             xml += "<newline/>"
1678                 xml += "</form>"
1679
1680             elif view_type == 'tree':
1681                 _rec_name = self._rec_name
1682                 if _rec_name not in self._columns:
1683                     _rec_name = self._columns.keys()[0]
1684                 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1685                        '<tree string="%s"><field name="%s"/></tree>' \
1686                        % (self._description, self._rec_name)
1687
1688             elif view_type == 'calendar':
1689                 xml = self.__get_default_calendar_view()
1690
1691             elif view_type == 'search':
1692                 xml = self.__get_default_search_view(cr, user, context)
1693
1694             else:
1695                 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1696                 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1697             result['arch'] = etree.fromstring(encode(xml))
1698             result['name'] = 'default'
1699             result['field_parent'] = False
1700             result['view_id'] = 0
1701
1702         xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1703         result['arch'] = xarch
1704         result['fields'] = xfields
1705
1706         if submenu:
1707             if context and context.get('active_id', False):
1708                 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1709                 if data_menu:
1710                     act_id = data_menu.id
1711                     if act_id:
1712                         data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1713                         result['submenu'] = getattr(data_action, 'menus', False)
1714         if toolbar:
1715             def clean(x):
1716                 x = x[2]
1717                 for key in ('report_sxw_content', 'report_rml_content',
1718                         'report_sxw', 'report_rml',
1719                         'report_sxw_content_data', 'report_rml_content_data'):
1720                     if key in x:
1721                         del x[key]
1722                 return x
1723             ir_values_obj = self.pool.get('ir.values')
1724             resprint = ir_values_obj.get(cr, user, 'action',
1725                     'client_print_multi', [(self._name, False)], False,
1726                     context)
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                         multi_fields = res2.get(record['id'],{})
3111                         if multi_fields:
3112                             record[pos] = multi_fields.get(pos,[])
3113             else:
3114                 for f in val:
3115                     res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3116                     for record in res:
3117                         if res2:
3118                             record[f] = res2[record['id']]
3119                         else:
3120                             record[f] = []
3121         readonly = None
3122         for vals in res:
3123             for field in vals.copy():
3124                 fobj = None
3125                 if field in self._columns:
3126                     fobj = self._columns[field]
3127
3128                 if not fobj:
3129                     continue
3130                 groups = fobj.read
3131                 if groups:
3132                     edit = False
3133                     for group in groups:
3134                         module = group.split(".")[0]
3135                         grp = group.split(".")[1]
3136                         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"  \
3137                                    (grp, module, 'res.groups', user))
3138                         readonly = cr.fetchall()
3139                         if readonly[0][0] >= 1:
3140                             edit = True
3141                             break
3142                         elif readonly[0][0] == 0:
3143                             edit = False
3144                         else:
3145                             edit = False
3146
3147                     if not edit:
3148                         if type(vals[field]) == type([]):
3149                             vals[field] = []
3150                         elif type(vals[field]) == type(0.0):
3151                             vals[field] = 0
3152                         elif type(vals[field]) == type(''):
3153                             vals[field] = '=No Permission='
3154                         else:
3155                             vals[field] = False
3156         return res
3157
3158     def perm_read(self, cr, user, ids, context=None, details=True):
3159         """
3160         Returns some metadata about the given records.
3161
3162         :param details: if True, \*_uid fields are replaced with the name of the user
3163         :return: list of ownership dictionaries for each requested record
3164         :rtype: list of dictionaries with the following keys:
3165
3166                     * id: object id
3167                     * create_uid: user who created the record
3168                     * create_date: date when the record was created
3169                     * write_uid: last user who changed the record
3170                     * write_date: date of the last change to the record
3171                     * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3172         """
3173         if not context:
3174             context = {}
3175         if not ids:
3176             return []
3177         fields = ''
3178         uniq = isinstance(ids, (int, long))
3179         if uniq:
3180             ids = [ids]
3181         fields = ['id']
3182         if self._log_access:
3183             fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3184         quoted_table = '"%s"' % self._table
3185         fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3186         query = '''SELECT %s, __imd.module, __imd.name
3187                    FROM %s LEFT JOIN ir_model_data __imd
3188                        ON (__imd.model = %%s and __imd.res_id = %s.id)
3189                    WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3190         cr.execute(query, (self._name, tuple(ids)))
3191         res = cr.dictfetchall()
3192         for r in res:
3193             for key in r:
3194                 r[key] = r[key] or False
3195                 if details and key in ('write_uid', 'create_uid'):
3196                     if r[key]:
3197                         r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3198             r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3199             del r['name'], r['module']
3200         if uniq:
3201             return res[ids[0]]
3202         return res
3203
3204     def _check_concurrency(self, cr, ids, context):
3205         if not context:
3206             return
3207         if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
3208             def key(oid):
3209                 return "%s,%s" % (self._name, oid)
3210             santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3211             for i in range(0, len(ids), cr.IN_MAX):
3212                 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
3213                                           for oid in ids[i:i+cr.IN_MAX]
3214                                           if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
3215                 if sub_ids:
3216                     cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
3217                     res = cr.fetchone()
3218                     if res and res[0]:
3219                         raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
3220
3221     def check_access_rule(self, cr, uid, ids, operation, context=None):
3222         """Verifies that the operation given by ``operation`` is allowed for the user
3223            according to ir.rules.
3224
3225            :param operation: one of ``write``, ``unlink``
3226            :raise except_orm: * if current ir.rules do not permit this operation.
3227            :return: None if the operation is allowed
3228         """
3229         where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3230         if where_clause:
3231             where_clause = ' and ' + ' and '.join(where_clause)
3232             for sub_ids in cr.split_for_in_conditions(ids):
3233                 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3234                            ' WHERE ' + self._table + '.id IN %s' + where_clause,
3235                            [sub_ids] + where_params)
3236                 if cr.rowcount != len(sub_ids):
3237                     raise except_orm(_('AccessError'),
3238                                      _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
3239                                      % (operation, self._name))
3240
3241     def unlink(self, cr, uid, ids, context=None):
3242         """
3243         Delete records with given ids
3244
3245         :param cr: database cursor
3246         :param uid: current user id
3247         :param ids: id or list of ids
3248         :param context: (optional) context arguments, like lang, time zone
3249         :return: True
3250         :raise AccessError: * if user has no unlink rights on the requested object
3251                             * if user tries to bypass access rules for unlink on the requested object
3252         :raise UserError: if the record is default property for other records
3253
3254         """
3255         if not ids:
3256             return True
3257         if isinstance(ids, (int, long)):
3258             ids = [ids]
3259
3260         result_store = self._store_get_values(cr, uid, ids, None, context)
3261
3262         self._check_concurrency(cr, ids, context)
3263
3264         self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3265
3266         properties = self.pool.get('ir.property')
3267         domain = [('res_id', '=', False),
3268                   ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3269                  ]
3270         if properties.search(cr, uid, domain, context=context):
3271             raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3272
3273         wf_service = netsvc.LocalService("workflow")
3274         for oid in ids:
3275             wf_service.trg_delete(uid, self._name, oid, cr)
3276
3277
3278         self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3279         for sub_ids in cr.split_for_in_conditions(ids):
3280             cr.execute('delete from ' + self._table + ' ' \
3281                        'where id IN %s', (sub_ids,))
3282         for order, object, store_ids, fields in result_store:
3283             if object != self._name:
3284                 obj = self.pool.get(object)
3285                 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3286                 rids = map(lambda x: x[0], cr.fetchall())
3287                 if rids:
3288                     obj._store_set_values(cr, uid, rids, fields, context)
3289         return True
3290
3291     #
3292     # TODO: Validate
3293     #
3294     def write(self, cr, user, ids, vals, context=None):
3295         """
3296         Update records with given ids with the given field values
3297
3298         :param cr: database cursor
3299         :param user: current user id
3300         :type user: integer
3301         :param ids: object id or list of object ids to update according to **vals**
3302         :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3303         :type vals: dictionary
3304         :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3305         :type context: dictionary
3306         :return: True
3307         :raise AccessError: * if user has no write rights on the requested object
3308                             * if user tries to bypass access rules for write on the requested object
3309         :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3310         :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)
3311
3312         **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3313
3314             + For a many2many field, a list of tuples is expected.
3315               Here is the list of tuple that are accepted, with the corresponding semantics ::
3316
3317                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
3318                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
3319                  (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)
3320                  (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)
3321                  (4, ID)                link to existing record with id = ID (adds a relationship)
3322                  (5)                    unlink all (like using (3,ID) for all linked records)
3323                  (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3324
3325                  Example:
3326                     [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3327
3328             + For a one2many field, a lits of tuples is expected.
3329               Here is the list of tuple that are accepted, with the corresponding semantics ::
3330
3331                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
3332                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
3333                  (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)
3334
3335                  Example:
3336                     [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3337
3338             + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3339             + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3340
3341         """
3342         readonly = None
3343         for field in vals.copy():
3344             fobj = None
3345             if field in self._columns:
3346                 fobj = self._columns[field]
3347             else:
3348                 fobj = self._inherit_fields[field][2]
3349             if not fobj:
3350                 continue
3351             groups = fobj.write
3352
3353             if groups:
3354                 edit = False
3355                 for group in groups:
3356                     module = group.split(".")[0]
3357                     grp = group.split(".")[1]
3358                     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", \
3359                                (grp, module, 'res.groups', user))
3360                     readonly = cr.fetchall()
3361                     if readonly[0][0] >= 1:
3362                         edit = True
3363                         break
3364                     elif readonly[0][0] == 0:
3365                         edit = False
3366                     else:
3367                         edit = False
3368
3369                 if not edit:
3370                     vals.pop(field)
3371
3372         if not context:
3373             context = {}
3374         if not ids:
3375             return True
3376         if isinstance(ids, (int, long)):
3377             ids = [ids]
3378
3379         self._check_concurrency(cr, ids, context)
3380         self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3381
3382         result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3383
3384         # No direct update of parent_left/right
3385         vals.pop('parent_left', None)
3386         vals.pop('parent_right', None)
3387
3388         parents_changed = []
3389         if self._parent_store and (self._parent_name in vals):
3390             # The parent_left/right computation may take up to
3391             # 5 seconds. No need to recompute the values if the
3392             # parent is the same. Get the current value of the parent
3393             parent_val = vals[self._parent_name]
3394             if parent_val:
3395                 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3396                                 (self._table, self._parent_name, self._parent_name)
3397                 cr.execute(query, (tuple(ids), parent_val))
3398             else:
3399                 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3400                                 (self._table, self._parent_name)
3401                 cr.execute(query, (tuple(ids),))
3402             parents_changed = map(operator.itemgetter(0), cr.fetchall())
3403
3404         upd0 = []
3405         upd1 = []
3406         upd_todo = []
3407         updend = []
3408         direct = []
3409         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3410         for field in vals:
3411             if field in self._columns:
3412                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3413                     if (not totranslate) or not self._columns[field].translate:
3414                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3415                         upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3416                     direct.append(field)
3417                 else:
3418                     upd_todo.append(field)
3419             else:
3420                 updend.append(field)
3421             if field in self._columns \
3422                     and hasattr(self._columns[field], 'selection') \
3423                     and vals[field]:
3424                 if self._columns[field]._type == 'reference':
3425                     val = vals[field].split(',')[0]
3426                 else:
3427                     val = vals[field]
3428                 if isinstance(self._columns[field].selection, (tuple, list)):
3429                     if val not in dict(self._columns[field].selection):
3430                         raise except_orm(_('ValidateError'),
3431                         _('The value "%s" for the field "%s" is not in the selection') \
3432                                 % (vals[field], field))
3433                 else:
3434                     if val not in dict(self._columns[field].selection(
3435                         self, cr, user, context=context)):
3436                         raise except_orm(_('ValidateError'),
3437                         _('The value "%s" for the field "%s" is not in the selection') \
3438                                 % (vals[field], field))
3439
3440         if self._log_access:
3441             upd0.append('write_uid=%s')
3442             upd0.append('write_date=now()')
3443             upd1.append(user)
3444
3445         if len(upd0):
3446             self.check_access_rule(cr, user, ids, 'write', context=context)
3447             for sub_ids in cr.split_for_in_conditions(ids):
3448                 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3449                            'where id IN %s', upd1 + [sub_ids])
3450
3451             if totranslate:
3452                 # TODO: optimize
3453                 for f in direct:
3454                     if self._columns[f].translate:
3455                         src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3456                         if not src_trans:
3457                             src_trans = vals[f]
3458                             # Inserting value to DB
3459                             self.write(cr, user, ids, {f: vals[f]})
3460                         self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3461
3462
3463         # call the 'set' method of fields which are not classic_write
3464         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3465
3466         # default element in context must be removed when call a one2many or many2many
3467         rel_context = context.copy()
3468         for c in context.items():
3469             if c[0].startswith('default_'):
3470                 del rel_context[c[0]]
3471
3472         for field in upd_todo:
3473             for id in ids:
3474                 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3475
3476         for table in self._inherits:
3477             col = self._inherits[table]
3478             nids = []
3479             for sub_ids in cr.split_for_in_conditions(ids):
3480                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3481                            'where id IN %s', (sub_ids,))
3482                 nids.extend([x[0] for x in cr.fetchall()])
3483
3484             v = {}
3485             for val in updend:
3486                 if self._inherit_fields[val][0] == table:
3487                     v[val] = vals[val]
3488             if v:
3489                 self.pool.get(table).write(cr, user, nids, v, context)
3490
3491         self._validate(cr, user, ids, context)
3492
3493         # TODO: use _order to set dest at the right position and not first node of parent
3494         # We can't defer parent_store computation because the stored function
3495         # fields that are computer may refer (directly or indirectly) to
3496         # parent_left/right (via a child_of domain)
3497         if parents_changed:
3498             if self.pool._init:
3499                 self.pool._init_parent[self._name] = True
3500             else:
3501                 order = self._parent_order or self._order
3502                 parent_val = vals[self._parent_name]
3503                 if parent_val:
3504                     clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3505                 else:
3506                     clause, params = '%s IS NULL' % (self._parent_name,), ()
3507                 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3508                 parents = cr.fetchall()
3509
3510                 for id in parents_changed:
3511                     cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3512                     pleft, pright = cr.fetchone()
3513                     distance = pright - pleft + 1
3514
3515                     # Find Position of the element
3516                     position = None
3517                     for (parent_pright, parent_id) in parents:
3518                         if parent_id == id:
3519                             break
3520                         position = parent_pright + 1
3521
3522                     # It's the first node of the parent
3523                     if not position:
3524                         if not parent_val:
3525                             position = 1
3526                         else:
3527                             cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3528                             position = cr.fetchone()[0] + 1
3529
3530                     if pleft < position <= pright:
3531                         raise except_orm(_('UserError'), _('Recursivity Detected.'))
3532
3533                     if pleft < position:
3534                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3535                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3536                         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))
3537                     else:
3538                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3539                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3540                         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))
3541
3542         result += self._store_get_values(cr, user, ids, vals.keys(), context)
3543         result.sort()
3544
3545         done = {}
3546         for order, object, ids, fields in result:
3547             key = (object, tuple(fields))
3548             done.setdefault(key, {})
3549             # avoid to do several times the same computation
3550             todo = []
3551             for id in ids:
3552                 if id not in done[key]:
3553                     done[key][id] = True
3554                     todo.append(id)
3555             self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3556
3557         wf_service = netsvc.LocalService("workflow")
3558         for id in ids:
3559             wf_service.trg_write(user, self._name, id, cr)
3560         return True
3561
3562     #
3563     # TODO: Should set perm to user.xxx
3564     #
3565     def create(self, cr, user, vals, context=None):
3566         """
3567         Create new record with specified value
3568
3569         :param cr: database cursor
3570         :param user: current user id
3571         :type user: integer
3572         :param vals: field values for new record, e.g {'field_name': field_value, ...}
3573         :type vals: dictionary
3574         :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3575         :type context: dictionary
3576         :return: id of new record created
3577         :raise AccessError: * if user has no create rights on the requested object
3578                             * if user tries to bypass access rules for create on the requested object
3579         :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3580         :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)
3581
3582         **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3583         Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3584         to specify them.
3585
3586         """
3587         if not context:
3588             context = {}
3589         self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3590
3591         vals = self._add_missing_default_values(cr, user, vals, context)
3592
3593         tocreate = {}
3594         for v in self._inherits:
3595             if self._inherits[v] not in vals:
3596                 tocreate[v] = {}
3597             else:
3598                 tocreate[v] = {'id': vals[self._inherits[v]]}
3599         (upd0, upd1, upd2) = ('', '', [])
3600         upd_todo = []
3601         for v in vals.keys():
3602             if v in self._inherit_fields:
3603                 (table, col, col_detail) = self._inherit_fields[v]
3604                 tocreate[table][v] = vals[v]
3605                 del vals[v]
3606             else:
3607                 if (v not in self._inherit_fields) and (v not in self._columns):
3608                     del vals[v]
3609
3610         # Try-except added to filter the creation of those records whose filds are readonly.
3611         # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3612         try:
3613             cr.execute("SELECT nextval('"+self._sequence+"')")
3614         except:
3615             raise except_orm(_('UserError'),
3616                         _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3617
3618         id_new = cr.fetchone()[0]
3619         for table in tocreate:
3620             if self._inherits[table] in vals:
3621                 del vals[self._inherits[table]]
3622
3623             record_id = tocreate[table].pop('id', None)
3624
3625             if record_id is None or not record_id:
3626                 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3627             else:
3628                 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3629
3630             upd0 += ',' + self._inherits[table]
3631             upd1 += ',%s'
3632             upd2.append(record_id)
3633
3634         #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3635         bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3636
3637         for bool_field in bool_fields:
3638             if bool_field not in vals:
3639                 vals[bool_field] = False
3640         #End
3641         for field in vals.copy():
3642             fobj = None
3643             if field in self._columns:
3644                 fobj = self._columns[field]
3645             else:
3646                 fobj = self._inherit_fields[field][2]
3647             if not fobj:
3648                 continue
3649             groups = fobj.write
3650             if groups:
3651                 edit = False
3652                 for group in groups:
3653                     module = group.split(".")[0]
3654                     grp = group.split(".")[1]
3655                     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" % \
3656                                (grp, module, 'res.groups', user))
3657                     readonly = cr.fetchall()
3658                     if readonly[0][0] >= 1:
3659                         edit = True
3660                         break
3661                     elif readonly[0][0] == 0:
3662                         edit = False
3663                     else:
3664                         edit = False
3665
3666                 if not edit:
3667                     vals.pop(field)
3668         for field in vals:
3669             if self._columns[field]._classic_write:
3670                 upd0 = upd0 + ',"' + field + '"'
3671                 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3672                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3673             else:
3674                 if not isinstance(self._columns[field], fields.related):
3675                     upd_todo.append(field)
3676             if field in self._columns \
3677                     and hasattr(self._columns[field], 'selection') \
3678                     and vals[field]:
3679                 if self._columns[field]._type == 'reference':
3680                     val = vals[field].split(',')[0]
3681                 else:
3682                     val = vals[field]
3683                 if isinstance(self._columns[field].selection, (tuple, list)):
3684                     if val not in dict(self._columns[field].selection):
3685                         raise except_orm(_('ValidateError'),
3686                         _('The value "%s" for the field "%s" is not in the selection') \
3687                                 % (vals[field], field))
3688                 else:
3689                     if val not in dict(self._columns[field].selection(
3690                         self, cr, user, context=context)):
3691                         raise except_orm(_('ValidateError'),
3692                         _('The value "%s" for the field "%s" is not in the selection') \
3693                                 % (vals[field], field))
3694         if self._log_access:
3695             upd0 += ',create_uid,create_date'
3696             upd1 += ',%s,now()'
3697             upd2.append(user)
3698         cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3699         self.check_access_rule(cr, user, [id_new], 'create', context=context)
3700         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3701
3702         if self._parent_store and not context.get('defer_parent_store_computation'):
3703             if self.pool._init:
3704                 self.pool._init_parent[self._name] = True
3705             else:
3706                 parent = vals.get(self._parent_name, False)
3707                 if parent:
3708                     cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3709                     pleft_old = None
3710                     result_p = cr.fetchall()
3711                     for (pleft,) in result_p:
3712                         if not pleft:
3713                             break
3714                         pleft_old = pleft
3715                     if not pleft_old:
3716                         cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3717                         pleft_old = cr.fetchone()[0]
3718                     pleft = pleft_old
3719                 else:
3720                     cr.execute('select max(parent_right) from '+self._table)
3721                     pleft = cr.fetchone()[0] or 0
3722                 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3723                 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3724                 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3725
3726         # default element in context must be remove when call a one2many or many2many
3727         rel_context = context.copy()
3728         for c in context.items():
3729             if c[0].startswith('default_'):
3730                 del rel_context[c[0]]
3731
3732         result = []
3733         for field in upd_todo:
3734             result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3735         self._validate(cr, user, [id_new], context)
3736
3737         if not context.get('no_store_function', False):
3738             result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3739             result.sort()
3740             done = []
3741             for order, object, ids, fields2 in result:
3742                 if not (object, ids, fields2) in done:
3743                     self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3744                     done.append((object, ids, fields2))
3745
3746         if self._log_create and not (context and context.get('no_store_function', False)):
3747             message = self._description + \
3748                 " '" + \
3749                 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3750                 "' " + _("created.")
3751             self.log(cr, user, id_new, message, True, context=context)
3752         wf_service = netsvc.LocalService("workflow")
3753         wf_service.trg_create(user, self._name, id_new, cr)
3754         return id_new
3755
3756     def _store_get_values(self, cr, uid, ids, fields, context):
3757         result = {}
3758         fncts = self.pool._store_function.get(self._name, [])
3759         for fnct in range(len(fncts)):
3760             if fncts[fnct][3]:
3761                 ok = False
3762                 if not fields:
3763                     ok = True
3764                 for f in (fields or []):
3765                     if f in fncts[fnct][3]:
3766                         ok = True
3767                         break
3768                 if not ok:
3769                     continue
3770
3771             result.setdefault(fncts[fnct][0], {})
3772
3773             # uid == 1 for accessing objects having rules defined on store fields
3774             ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3775             for id in filter(None, ids2):
3776                 result[fncts[fnct][0]].setdefault(id, [])
3777                 result[fncts[fnct][0]][id].append(fnct)
3778         dict = {}
3779         for object in result:
3780             k2 = {}
3781             for id, fnct in result[object].items():
3782                 k2.setdefault(tuple(fnct), [])
3783                 k2[tuple(fnct)].append(id)
3784             for fnct, id in k2.items():
3785                 dict.setdefault(fncts[fnct[0]][4], [])
3786                 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3787         result2 = []
3788         tmp = dict.keys()
3789         tmp.sort()
3790         for k in tmp:
3791             result2 += dict[k]
3792         return result2
3793
3794     def _store_set_values(self, cr, uid, ids, fields, context):
3795         if not ids:
3796             return True
3797         field_flag = False
3798         field_dict = {}
3799         if self._log_access:
3800             cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3801             res = cr.fetchall()
3802             for r in res:
3803                 if r[1]:
3804                     field_dict.setdefault(r[0], [])
3805                     res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3806                     write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3807                     for i in self.pool._store_function.get(self._name, []):
3808                         if i[5]:
3809                             up_write_date = write_date + datetime.timedelta(hours=i[5])
3810                             if datetime.datetime.now() < up_write_date:
3811                                 if i[1] in fields:
3812                                     field_dict[r[0]].append(i[1])
3813                                     if not field_flag:
3814                                         field_flag = True
3815         todo = {}
3816         keys = []
3817         for f in fields:
3818             if self._columns[f]._multi not in keys:
3819                 keys.append(self._columns[f]._multi)
3820             todo.setdefault(self._columns[f]._multi, [])
3821             todo[self._columns[f]._multi].append(f)
3822         for key in keys:
3823             val = todo[key]
3824             if key:
3825                 # uid == 1 for accessing objects having rules defined on store fields
3826                 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3827                 for id, value in result.items():
3828                     if field_flag:
3829                         for f in value.keys():
3830                             if f in field_dict[id]:
3831                                 value.pop(f)
3832                     upd0 = []
3833                     upd1 = []
3834                     for v in value:
3835                         if v not in val:
3836                             continue
3837                         if self._columns[v]._type in ('many2one', 'one2one'):
3838                             try:
3839                                 value[v] = value[v][0]
3840                             except:
3841                                 pass
3842                         upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3843                         upd1.append(self._columns[v]._symbol_set[1](value[v]))
3844                     upd1.append(id)
3845                     if upd0 and upd1:
3846                         cr.execute('update "' + self._table + '" set ' + \
3847                             ','.join(upd0) + ' where id = %s', upd1)
3848
3849             else:
3850                 for f in val:
3851                     # uid == 1 for accessing objects having rules defined on store fields
3852                     result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3853                     for r in result.keys():
3854                         if field_flag:
3855                             if r in field_dict.keys():
3856                                 if f in field_dict[r]:
3857                                     result.pop(r)
3858                     for id, value in result.items():
3859                         if self._columns[f]._type in ('many2one', 'one2one'):
3860                             try:
3861                                 value = value[0]
3862                             except:
3863                                 pass
3864                         cr.execute('update "' + self._table + '" set ' + \
3865                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3866         return True
3867
3868     #
3869     # TODO: Validate
3870     #
3871     def perm_write(self, cr, user, ids, fields, context=None):
3872         raise NotImplementedError(_('This method does not exist anymore'))
3873
3874     # TODO: ameliorer avec NULL
3875     def _where_calc(self, cr, user, domain, active_test=True, context=None):
3876         """Computes the WHERE clause needed to implement an OpenERP domain.
3877         :param domain: the domain to compute
3878         :type domain: list
3879         :param active_test: whether the default filtering of records with ``active``
3880                             field set to ``False`` should be applied.
3881         :return: the query expressing the given domain as provided in domain
3882         :rtype: osv.query.Query
3883         """
3884         if not context:
3885             context = {}
3886         domain = domain[:]
3887         # if the object has a field named 'active', filter out all inactive
3888         # records unless they were explicitely asked for
3889         if 'active' in self._columns and (active_test and context.get('active_test', True)):
3890             if domain:
3891                 active_in_args = False
3892                 for a in domain:
3893                     if a[0] == 'active':
3894                         active_in_args = True
3895                 if not active_in_args:
3896                     domain.insert(0, ('active', '=', 1))
3897             else:
3898                 domain = [('active', '=', 1)]
3899
3900         if domain:
3901             import expression
3902             e = expression.expression(domain)
3903             e.parse(cr, user, self, context)
3904             tables = e.get_tables()
3905             where_clause, where_params = e.to_sql()
3906             where_clause = where_clause and [where_clause] or []
3907         else:
3908             where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3909
3910         return Query(tables, where_clause, where_params)
3911
3912     def _check_qorder(self, word):
3913         if not regex_order.match(word):
3914             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)'))
3915         return True
3916
3917     def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3918         """Add what's missing in ``query`` to implement all appropriate ir.rules
3919           (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3920
3921            :param query: the current query object
3922         """
3923         def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3924             if added_clause:
3925                 if parent_model and child_object:
3926                     # as inherited rules are being applied, we need to add the missing JOIN
3927                     # to reach the parent table (if it was not JOINed yet in the query)
3928                     child_object._inherits_join_add(parent_model, query)
3929                 query.where_clause += added_clause
3930                 query.where_clause_params += added_params
3931                 for table in added_tables:
3932                     if table not in query.tables:
3933                         query.tables.append(table)
3934                 return True
3935             return False
3936
3937         # apply main rules on the object
3938         rule_obj = self.pool.get('ir.rule')
3939         apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3940
3941         # apply ir.rules from the parents (through _inherits)
3942         for inherited_model in self._inherits:
3943             kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3944             apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3945
3946     def _generate_m2o_order_by(self, order_field, query):
3947         """
3948         Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3949         either native m2o fields or function/related fields that are stored, including
3950         intermediate JOINs for inheritance if required.
3951
3952         :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3953         """
3954         if order_field not in self._columns and order_field in self._inherit_fields:
3955             # also add missing joins for reaching the table containing the m2o field
3956             qualified_field = self._inherits_join_calc(order_field, query)
3957             order_field_column = self._inherit_fields[order_field][2]
3958         else:
3959             qualified_field = '"%s"."%s"' % (self._table, order_field)
3960             order_field_column = self._columns[order_field]
3961
3962         assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3963         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"
3964
3965         # figure out the applicable order_by for the m2o
3966         dest_model = self.pool.get(order_field_column._obj)
3967         m2o_order = dest_model._order
3968         if not regex_order.match(m2o_order):
3969             # _order is complex, can't use it here, so we default to _rec_name
3970             m2o_order = dest_model._rec_name
3971         else:
3972             # extract the first field name, to be able to qualify it and add desc/asc
3973             m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
3974
3975         # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3976         # as we don't want to exclude results that have NULL values for the m2o
3977         src_table, src_field = qualified_field.replace('"','').split('.', 1)
3978         query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3979         return '"%s"."%s"' % (dest_model._table, m2o_order)
3980
3981
3982     def _generate_order_by(self, order_spec, query):
3983         """
3984         Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
3985         a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
3986
3987         :raise" except_orm in case order_spec is malformed
3988         """
3989         order_by_clause = self._order
3990         if order_spec:
3991             order_by_elements = []
3992             self._check_qorder(order_spec)
3993             for order_part in order_spec.split(','):
3994                 order_split = order_part.strip().split(' ')
3995                 order_field = order_split[0].strip()
3996                 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
3997                 if order_field in self._columns:
3998                     order_column = self._columns[order_field]
3999                     if order_column._classic_read:
4000                         order_by_clause = '"%s"."%s"' % (self._table, order_field)
4001                     elif order_column._type == 'many2one':
4002                         order_by_clause = self._generate_m2o_order_by(order_field, query)
4003                     else:
4004                         continue # ignore non-readable or "non-joignable" fields
4005                 elif order_field in self._inherit_fields:
4006                     parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4007                     order_column = parent_obj._columns[order_field]
4008                     if order_column._classic_read:
4009                         order_by_clause = self._inherits_join_calc(order_field, query)
4010                     elif order_column._type == 'many2one':
4011                         order_by_clause = self._generate_m2o_order_by(order_field, query)
4012                     else:
4013                         continue # ignore non-readable or "non-joignable" fields
4014                 order_by_elements.append("%s %s" % (order_by_clause, order_direction))
4015             order_by_clause = ",".join(order_by_elements)
4016
4017         return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4018
4019     def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4020         """
4021         Private implementation of search() method, allowing specifying the uid to use for the access right check.
4022         This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4023         by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4024         This is ok at the security level because this method is private and not callable through XML-RPC.
4025
4026         :param access_rights_uid: optional user ID to use when checking access rights
4027                                   (not for ir.rules, this is only for ir.model.access)
4028         """
4029         if context is None:
4030             context = {}
4031         self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4032
4033         query = self._where_calc(cr, user, args, context=context)
4034         self._apply_ir_rules(cr, user, query, 'read', context=context)
4035         order_by = self._generate_order_by(order, query)
4036         from_clause, where_clause, where_clause_params = query.get_sql()
4037
4038         limit_str = limit and ' limit %d' % limit or ''
4039         offset_str = offset and ' offset %d' % offset or ''
4040         where_str = where_clause and (" WHERE %s" % where_clause) or ''
4041
4042         if count:
4043             cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4044             res = cr.fetchall()
4045             return res[0][0]
4046         cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4047         res = cr.fetchall()
4048         return [x[0] for x in res]
4049
4050     # returns the different values ever entered for one field
4051     # this is used, for example, in the client when the user hits enter on
4052     # a char field
4053     def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4054         if not args:
4055             args = []
4056         if field in self._inherit_fields:
4057             return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4058         else:
4059             return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4060
4061     def copy_data(self, cr, uid, id, default=None, context=None):
4062         """
4063         Copy given record's data with all its fields values
4064
4065         :param cr: database cursor
4066         :param user: current user id
4067         :param id: id of the record to copy
4068         :param default: field values to override in the original values of the copied record
4069         :type default: dictionary
4070         :param context: context arguments, like lang, time zone
4071         :type context: dictionary
4072         :return: dictionary containing all the field values
4073         """
4074
4075         if context is None:
4076             context = {}
4077         if default is None:
4078             default = {}
4079         if 'state' not in default:
4080             if 'state' in self._defaults:
4081                 if callable(self._defaults['state']):
4082                     default['state'] = self._defaults['state'](self, cr, uid, context)
4083                 else:
4084                     default['state'] = self._defaults['state']
4085
4086         context_wo_lang = context
4087         if 'lang' in context:
4088             del context_wo_lang['lang']
4089         data = self.read(cr, uid, [id], context=context_wo_lang)[0]
4090
4091         fields = self.fields_get(cr, uid, context=context)
4092         for f in fields:
4093             ftype = fields[f]['type']
4094
4095             if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4096                 del data[f]
4097
4098             if f in default:
4099                 data[f] = default[f]
4100             elif ftype == 'function':
4101                 del data[f]
4102             elif ftype == 'many2one':
4103                 try:
4104                     data[f] = data[f] and data[f][0]
4105                 except:
4106                     pass
4107             elif ftype in ('one2many', 'one2one'):
4108                 res = []
4109                 rel = self.pool.get(fields[f]['relation'])
4110                 if data[f]:
4111                     # duplicate following the order of the ids
4112                     # because we'll rely on it later for copying
4113                     # translations in copy_translation()!
4114                     data[f].sort()
4115                     for rel_id in data[f]:
4116                         # the lines are first duplicated using the wrong (old)
4117                         # parent but then are reassigned to the correct one thanks
4118                         # to the (0, 0, ...)
4119                         d = rel.copy_data(cr, uid, rel_id, context=context)
4120                         res.append((0, 0, d))
4121                 data[f] = res
4122             elif ftype == 'many2many':
4123                 data[f] = [(6, 0, data[f])]
4124
4125         del data['id']
4126
4127         # make sure we don't break the current parent_store structure and
4128         # force a clean recompute!
4129         for parent_column in ['parent_left', 'parent_right']:
4130             data.pop(parent_column, None)
4131
4132         for v in self._inherits:
4133             del data[self._inherits[v]]
4134         return data
4135
4136     def copy_translations(self, cr, uid, old_id, new_id, context=None):
4137         trans_obj = self.pool.get('ir.translation')
4138         fields = self.fields_get(cr, uid, context=context)
4139
4140         translation_records = []
4141         for field_name, field_def in fields.items():
4142             # we must recursively copy the translations for o2o and o2m
4143             if field_def['type'] in ('one2one', 'one2many'):
4144                 target_obj = self.pool.get(field_def['relation'])
4145                 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4146                 # here we rely on the order of the ids to match the translations
4147                 # as foreseen in copy_data()
4148                 old_childs = sorted(old_record[field_name])
4149                 new_childs = sorted(new_record[field_name])
4150                 for (old_child, new_child) in zip(old_childs, new_childs):
4151                     # recursive copy of translations here
4152                     target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4153             # and for translatable fields we keep them for copy
4154             elif field_def.get('translate'):
4155                 trans_name = ''
4156                 if field_name in self._columns:
4157                     trans_name = self._name + "," + field_name
4158                 elif field_name in self._inherit_fields:
4159                     trans_name = self._inherit_fields[field_name][0] + "," + field_name
4160                 if trans_name:
4161                     trans_ids = trans_obj.search(cr, uid, [
4162                             ('name', '=', trans_name),
4163                             ('res_id', '=', old_id)
4164                     ])
4165                     translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4166
4167         for record in translation_records:
4168             del record['id']
4169             record['res_id'] = new_id
4170             trans_obj.create(cr, uid, record, context=context)
4171
4172
4173     def copy(self, cr, uid, id, default=None, context=None):
4174         """
4175         Duplicate record with given id updating it with default values
4176
4177         :param cr: database cursor
4178         :param uid: current user id
4179         :param id: id of the record to copy
4180         :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4181         :type default: dictionary
4182         :param context: context arguments, like lang, time zone
4183         :type context: dictionary
4184         :return: True
4185
4186         """
4187         data = self.copy_data(cr, uid, id, default, context)
4188         new_id = self.create(cr, uid, data, context)
4189         self.copy_translations(cr, uid, id, new_id, context)
4190         return new_id
4191
4192     def exists(self, cr, uid, ids, context=None):
4193         if type(ids) in (int, long):
4194             ids = [ids]
4195         query = 'SELECT count(1) FROM "%s"' % (self._table)
4196         cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4197         return cr.fetchone()[0] == len(ids)
4198
4199     def check_recursion(self, cr, uid, ids, parent=None):
4200         """
4201         Verifies that there is no loop in a hierarchical structure of records,
4202         by following the parent relationship using the **parent** field until a loop
4203         is detected or until a top-level record is found.
4204
4205         :param cr: database cursor
4206         :param uid: current user id
4207         :param ids: list of ids of records to check
4208         :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4209         :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4210         """
4211
4212         if not parent:
4213             parent = self._parent_name
4214         ids_parent = ids[:]
4215         query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4216         while ids_parent:
4217             ids_parent2 = []
4218             for i in range(0, len(ids), cr.IN_MAX):
4219                 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4220                 cr.execute(query, (tuple(sub_ids_parent),))
4221                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4222             ids_parent = ids_parent2
4223             for i in ids_parent:
4224                 if i in ids:
4225                     return False
4226         return True
4227
4228     def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4229         """Find out the XML ID of any database record, if there
4230         is one. This method works as a possible implementation
4231         for a function field, to be able to add it to any
4232         model object easily, referencing it as ``osv.osv.get_xml_id``.
4233
4234         **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4235
4236         :return: the fully qualified XML ID of the given object,
4237                  defaulting to an empty string when there's none
4238                  (to be usable as a function field).
4239         """
4240         result = dict.fromkeys(ids, '')
4241         model_data_obj = self.pool.get('ir.model.data')
4242         data_ids = model_data_obj.search(cr, uid,
4243                 [('model', '=', self._name), ('res_id', 'in', ids)])
4244         data_results = model_data_obj.read(cr, uid, data_ids,
4245                 ['name', 'module', 'res_id'])
4246         for record in data_results:
4247             result[record['res_id']] = '%(module)s.%(name)s' % record
4248         return result
4249
4250 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
4251