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