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