[FIX] ORM: Defult_field name in context belongs to curent object only, not to its...
[odoo/odoo.git] / bin / osv / orm.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 #
24 # Object relationnal mapping to postgresql module
25 #    . Hierarchical structure
26 #    . Constraints consistency, validations
27 #    . Object meta Data depends on its status
28 #    . Optimised processing by complex query (multiple actions at once)
29 #    . Default fields value
30 #    . Permissions optimisation
31 #    . Persistant object: DB postgresql
32 #    . Datas conversions
33 #    . Multi-level caching system
34 #    . 2 different inheritancies
35 #    . Fields:
36 #         - classicals (varchar, integer, boolean, ...)
37 #         - relations (one2many, many2one, many2many)
38 #         - functions
39 #
40 #
41
42 import time
43 import calendar
44 import types
45 import string
46 import netsvc
47 import re
48
49 import pickle
50
51 import fields
52 import tools
53
54 import sys
55 try:
56     from xml import dom, xpath
57 except ImportError:
58     sys.stderr.write("ERROR: Import xpath module\n")
59     sys.stderr.write("ERROR: Try to install the old python-xml package\n")
60     sys.exit(2)
61
62 from tools.config import config
63
64 regex_order = re.compile('^([a-z0-9_]+( *desc| *asc)?( *, *|))+$', re.I)
65
66 def last_day_of_current_month():
67     import datetime
68     import calendar
69     today = datetime.date.today()
70     last_day = str(calendar.monthrange(today.year, today.month)[1])
71     return time.strftime('%Y-%m-' + last_day)
72
73 def intersect(la, lb):
74     return filter(lambda x: x in lb, la)
75
76
77 class except_orm(Exception):
78     def __init__(self, name, value):
79         self.name = name
80         self.value = value
81         self.args = (name, value)
82
83 class BrowseRecordError(Exception):
84     pass
85
86 # Readonly python database object browser
87 class browse_null(object):
88
89     def __init__(self):
90         self.id = False
91
92     def __getitem__(self, name):
93         return False
94
95     def __getattr__(self, name):
96         return False  # XXX: return self ?
97
98     def __int__(self):
99         return False
100
101     def __str__(self):
102         return ''
103
104     def __nonzero__(self):
105         return False
106
107     def __unicode__(self):
108         return u''
109
110
111 #
112 # TODO: execute an object method on browse_record_list
113 #
114 class browse_record_list(list):
115
116     def __init__(self, lst, context=None):
117         if not context:
118             context = {}
119         super(browse_record_list, self).__init__(lst)
120         self.context = context
121
122
123 class browse_record(object):
124     def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}):
125         '''
126         table : the object (inherited from orm)
127         context : a dictionary with an optional context
128         '''
129         if not context:
130             context = {}
131         self._list_class = list_class or browse_record_list
132         self._cr = cr
133         self._uid = uid
134         self._id = id
135         self._table = table
136         self._table_name = self._table._name
137         self._context = context
138         self._fields_process = fields_process
139
140         cache.setdefault(table._name, {})
141         self._data = cache[table._name]
142
143         if not (id and isinstance(id, (int, long,))):
144             raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
145 #        if not table.exists(cr, uid, id, context):
146 #            raise BrowseRecordError(_('Object %s does not exists') % (self,))
147
148         if id not in self._data:
149             self._data[id] = {'id': id}
150
151         self._cache = cache
152
153     def __getitem__(self, name):
154         if name == 'id':
155             return self._id
156         if name not in self._data[self._id]:
157             # build the list of fields we will fetch
158
159             # fetch the definition of the field which was asked for
160             if name in self._table._columns:
161                 col = self._table._columns[name]
162             elif name in self._table._inherit_fields:
163                 col = self._table._inherit_fields[name][2]
164             elif hasattr(self._table, name):
165                 if isinstance(getattr(self._table, name), (types.MethodType, types.LambdaType, types.FunctionType)):
166                     return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv)
167                 else:
168                     return getattr(self._table, name)
169             else:
170                 logger = netsvc.Logger()
171                 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name))
172                 return False
173
174             # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
175             if col._classic_write:
176                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
177                 ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items())
178                 # gen the list of inherited fields
179                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
180                 # complete the field list with the inherited fields which are classic or many2one
181                 ffields += filter(lambda x: x[1]._classic_write, inherits)
182             # otherwise we fetch only that field
183             else:
184                 ffields = [(name, col)]
185             ids = filter(lambda id: name not in self._data[id], self._data.keys())
186             # read the data
187             fffields = map(lambda x: x[0], ffields)
188             datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write")
189             if self._fields_process:
190                 lang = self._context.get('lang', 'en_US') or 'en_US'
191                 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
192                 if not lang_obj_ids:
193                     raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
194                 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
195                 for n, f in ffields:
196                     if f._type in self._fields_process:
197                         for d in datas:
198                             d[n] = self._fields_process[f._type](d[n])
199                             if (d[n] is not None) and (d[n] is not False):
200                                 d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj)
201
202
203             # create browse records for 'remote' objects
204             for data in datas:
205                 for n, f in ffields:
206                     if f._type in ('many2one', 'one2one'):
207                         if data[n]:
208                             obj = self._table.pool.get(f._obj)
209                             compids = False
210                             if type(data[n]) in (type([]),type( (1,) )):
211                                 ids2 = data[n][0]
212                             else:
213                                 ids2 = data[n]
214                             if ids2:
215                                 data[n] = browse_record(self._cr, self._uid, ids2, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
216                             else:
217                                 data[n] = browse_null()
218                         else:
219                             data[n] = browse_null()
220                     elif f._type in ('one2many', 'many2many') and len(data[n]):
221                         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)
222                 self._data[data['id']].update(data)
223         return self._data[self._id][name]
224
225     def __getattr__(self, name):
226 #       raise an AttributeError exception.
227         return self[name]
228
229     def __contains__(self, name):
230         return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
231
232     def __hasattr__(self, name):
233         return name in self
234
235     def __int__(self):
236         return self._id
237
238     def __str__(self):
239         return "browse_record(%s, %d)" % (self._table_name, self._id)
240
241     def __eq__(self, other):
242         return (self._table_name, self._id) == (other._table_name, other._id)
243
244     def __ne__(self, other):
245         return (self._table_name, self._id) != (other._table_name, other._id)
246
247     # we need to define __unicode__ even though we've already defined __str__
248     # because we have overridden __getattr__
249     def __unicode__(self):
250         return unicode(str(self))
251
252     def __hash__(self):
253         return hash((self._table_name, self._id))
254
255     __repr__ = __str__
256
257
258 def get_pg_type(f):
259     '''
260     returns a tuple
261     (type returned by postgres when the column was created, type expression to create the column)
262     '''
263
264     type_dict = {
265             fields.boolean: 'bool',
266             fields.integer: 'int4',
267             fields.integer_big: 'int8',
268             fields.text: 'text',
269             fields.date: 'date',
270             fields.time: 'time',
271             fields.datetime: 'timestamp',
272             fields.binary: 'bytea',
273             fields.many2one: 'int4',
274             }
275     if type(f) in type_dict:
276         f_type = (type_dict[type(f)], type_dict[type(f)])
277     elif isinstance(f, fields.float):
278         if f.digits:
279             f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1]))
280         else:
281             f_type = ('float8', 'DOUBLE PRECISION')
282     elif isinstance(f, (fields.char, fields.reference)):
283         f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
284     elif isinstance(f, fields.selection):
285         if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
286             f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
287         elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
288             f_size = -1
289         else:
290             f_size = (hasattr(f, 'size') and f.size) or 16
291
292         if f_size == -1:
293             f_type = ('int4', 'INTEGER')
294         else:
295             f_type = ('varchar', 'VARCHAR(%d)' % f_size)
296     elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict:
297         t = eval('fields.'+(f._type))
298         f_type = (type_dict[t], type_dict[t])
299     elif isinstance(f, fields.function) and f._type == 'float':
300         f_type = ('float8', 'DOUBLE PRECISION')
301     elif isinstance(f, fields.function) and f._type == 'selection':
302         f_type = ('text', 'text')
303     elif isinstance(f, fields.function) and f._type == 'char':
304         f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
305     else:
306         logger = netsvc.Logger()
307         logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
308         f_type = None
309     return f_type
310
311
312 class orm_template(object):
313     _name = None
314     _columns = {}
315     _constraints = []
316     _defaults = {}
317     _rec_name = 'name'
318     _parent_name = 'parent_id'
319     _parent_store = False
320     _parent_order = False
321     _date_name = 'date'
322     _order = 'id'
323     _sequence = None
324     _description = None
325     _inherits = {}
326     _table = None
327     _invalids = set()
328
329     CONCURRENCY_CHECK_FIELD = '__last_update'
330
331     def _field_create(self, cr, context={}):
332         cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
333         if not cr.rowcount:
334             cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
335             model_id = cr.fetchone()[0]
336             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'))
337         else:
338             model_id = cr.fetchone()[0]
339         if 'module' in context:
340             name_id = 'model_'+self._name.replace('.','_')
341             cr.execute('select * from ir_model_data where name=%s and res_id=%s', (name_id,model_id))
342             if not cr.rowcount:
343                 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
344                     (name_id, context['module'], 'ir.model', model_id)
345                 )
346
347         cr.commit()
348
349         cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
350         cols = {}
351         for rec in cr.dictfetchall():
352             cols[rec['name']] = rec
353
354         for (k, f) in self._columns.items():
355             vals = {
356                 'model_id': model_id,
357                 'model': self._name,
358                 'name': k,
359                 'field_description': f.string.replace("'", " "),
360                 'ttype': f._type,
361                 'relation': f._obj or 'NULL',
362                 'view_load': (f.view_load and 1) or 0,
363                 'select_level': str(f.select or 0),
364                 'readonly':(f.readonly and 1) or 0,
365                 'required':(f.required and 1) or 0,
366             }
367             if k not in cols:
368                 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
369                 id = cr.fetchone()[0]
370                 vals['id'] = id
371                 cr.execute("""INSERT INTO ir_model_fields (
372                     id, model_id, model, name, field_description, ttype,
373                     relation,view_load,state,select_level
374                 ) VALUES (
375                     %s,%s,%s,%s,%s,%s,%s,%s,%s,%s
376                 )""", (
377                     id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
378                      vals['relation'], bool(vals['view_load']), 'base',
379                     vals['select_level']
380                 ))
381                 if 'module' in context:
382                     cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
383                         (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id)
384                     )
385             else:
386                 for key, val in vals.items():
387                     if cols[k][key] != vals[key]:
388                         cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
389                         cr.commit()
390                         cr.execute("""UPDATE ir_model_fields SET
391                             model_id=%s, field_description=%s, ttype=%s, relation=%s,
392                             view_load=%s, select_level=%s, readonly=%s ,required=%s
393                         WHERE
394                             model=%s AND name=%s""", (
395                                 vals['model_id'], vals['field_description'], vals['ttype'],
396                                 vals['relation'], bool(vals['view_load']),
397                                 vals['select_level'], bool(vals['readonly']),bool(vals['required']), vals['model'], vals['name']
398                             ))
399                         continue
400         cr.commit()
401
402     def _auto_init(self, cr, context={}):
403         self._field_create(cr, context)
404
405     def __init__(self, cr):
406         if not self._name and not hasattr(self, '_inherit'):
407             name = type(self).__name__.split('.')[0]
408             msg = "The class %s has to have a _name attribute" % name
409
410             logger = netsvc.Logger()
411             logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
412             raise except_orm('ValueError', msg )
413
414         if not self._description:
415             self._description = self._name
416         if not self._table:
417             self._table = self._name.replace('.', '_')
418
419     def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
420         if not context:
421             context = {}
422         self._list_class = list_class or browse_record_list
423         cache = {}
424         # need to accepts ints and longs because ids coming from a method
425         # launched by button in the interface have a type long...
426         if isinstance(select, (int, long)):
427             return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
428         elif isinstance(select, list):
429             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)
430         else:
431             return browse_null()
432
433     def __export_row(self, cr, uid, row, fields, context=None):
434         lines = []
435         data = map(lambda x: '', range(len(fields)))
436         done = []
437         for fpos in range(len(fields)):
438             f = fields[fpos]
439             if f:
440                 r = row
441                 i = 0
442                 while i < len(f):
443                     r = r[f[i]]
444                     if not r:
445                         break
446                     if isinstance(r, (browse_record_list, list)):
447                         first = True
448                         fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
449                                 or [], fields)
450                         if fields2 in done:
451                             break
452                         done.append(fields2)
453                         for row2 in r:
454                             lines2 = self.__export_row(cr, uid, row2, fields2,
455                                     context)
456                             if first:
457                                 for fpos2 in range(len(fields)):
458                                     if lines2 and lines2[0][fpos2]:
459                                         data[fpos2] = lines2[0][fpos2]
460                                 lines += lines2[1:]
461                                 first = False
462                             else:
463                                 lines += lines2
464                         break
465                     i += 1
466                 if i == len(f):
467                     data[fpos] = tools.ustr(r or '')
468         return [data] + lines
469
470     def export_data(self, cr, uid, ids, fields, context=None):
471         if not context:
472             context = {}
473         fields = map(lambda x: x.split('/'), fields)
474         datas = []
475         for row in self.browse(cr, uid, ids, context):
476             datas += self.__export_row(cr, uid, row, fields, context)
477         return datas
478
479     def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
480         if not context:
481             context = {}
482         fields = map(lambda x: x.split('/'), fields)
483         logger = netsvc.Logger()
484
485         def process_liness(self, datas, prefix, fields_def, position=0):
486             line = datas[position]
487             row = {}
488             translate = {}
489             todo = []
490             warning = ''
491             data_id = False
492             #
493             # Import normal fields
494             #
495             for i in range(len(fields)):
496                 if i >= len(line):
497                     raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
498                 field = fields[i]
499                 if field == ["id"]:
500                     data_id = line[i]
501                     continue
502                 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
503                     res_id = False
504                     if line[i]:
505                         if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
506                             res_id = []
507                             for word in line[i].split(config.get('csv_internal_sep')):
508                                 if '.' in word:
509                                     module, xml_id = word.rsplit('.', 1)
510                                 else:
511                                     module, xml_id = current_module, word
512                                 ir_model_data_obj = self.pool.get('ir.model.data')
513                                 id = ir_model_data_obj._get_id(cr, uid, module,
514                                         xml_id)
515                                 res_id2 = ir_model_data_obj.read(cr, uid, [id],
516                                         ['res_id'])[0]['res_id']
517                                 if res_id2:
518                                     res_id.append(res_id2)
519                             if len(res_id):
520                                 res_id = [(6, 0, res_id)]
521                         else:
522                             if '.' in line[i]:
523                                 module, xml_id = line[i].rsplit('.', 1)
524                             else:
525                                 module, xml_id = current_module, line[i]
526                             ir_model_data_obj = self.pool.get('ir.model.data')
527                             id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
528                             res_id = ir_model_data_obj.read(cr, uid, [id],
529                                     ['res_id'])[0]['res_id']
530                     row[field[0][:-3]] = res_id or False
531                     continue
532                 if (len(field) == len(prefix)+1) and \
533                         len(field[len(prefix)].split(':lang=')) == 2:
534                     f, lang = field[len(prefix)].split(':lang=')
535                     translate.setdefault(lang, {})[f]=line[i] or False
536                     continue
537                 if (len(field) == len(prefix)+1) and \
538                         (prefix == field[0:len(prefix)]):
539                     if fields_def[field[len(prefix)]]['type'] == 'integer':
540                         res = line[i] and int(line[i])
541                     elif fields_def[field[len(prefix)]]['type'] == 'boolean':
542                         res = line[i] and eval(line[i])
543                     elif fields_def[field[len(prefix)]]['type'] == 'float':
544                         res = line[i] and float(line[i])
545                     elif fields_def[field[len(prefix)]]['type'] == 'selection':
546                         res = False
547                         if isinstance(fields_def[field[len(prefix)]]['selection'],
548                                 (tuple, list)):
549                             sel = fields_def[field[len(prefix)]]['selection']
550                         else:
551                             sel = fields_def[field[len(prefix)]]['selection'](self,
552                                     cr, uid, context)
553                         for key, val in sel:
554                             if str(key) == line[i]:
555                                 res = key
556                         if line[i] and not res:
557                             logger.notifyChannel("import", netsvc.LOG_WARNING,
558                                     "key '%s' not found in selection field '%s'" % \
559                                             (line[i], field[len(prefix)]))
560                     elif fields_def[field[len(prefix)]]['type']=='many2one':
561                         res = False
562                         if line[i]:
563                             relation = fields_def[field[len(prefix)]]['relation']
564                             res2 = self.pool.get(relation).name_search(cr, uid,
565                                     line[i], [], operator='=')
566                             res = (res2 and res2[0][0]) or False
567                             if not res:
568                                 warning += ('Relation not found: ' + line[i] + \
569                                         ' on ' + relation + ' !\n')
570                                 logger.notifyChannel("import", netsvc.LOG_WARNING,
571                                         'Relation not found: ' + line[i] + \
572                                                 ' on ' + relation + ' !\n')
573                     elif fields_def[field[len(prefix)]]['type']=='many2many':
574                         res = []
575                         if line[i]:
576                             relation = fields_def[field[len(prefix)]]['relation']
577                             for word in line[i].split(config.get('csv_internal_sep')):
578                                 res2 = self.pool.get(relation).name_search(cr,
579                                         uid, word, [], operator='=')
580                                 res3 = (res2 and res2[0][0]) or False
581                                 if not res3:
582                                     warning += ('Relation not found: ' + \
583                                             line[i] + ' on '+relation + ' !\n')
584                                     logger.notifyChannel("import",
585                                             netsvc.LOG_WARNING,
586                                             'Relation not found: ' + line[i] + \
587                                                     ' on '+relation + ' !\n')
588                                 else:
589                                     res.append(res3)
590                             if len(res):
591                                 res = [(6, 0, res)]
592                     else:
593                         res = line[i] or False
594                     row[field[len(prefix)]] = res
595                 elif (prefix==field[0:len(prefix)]):
596                     if field[0] not in todo:
597                         todo.append(field[len(prefix)])
598             #
599             # Import one2many fields
600             #
601             nbrmax = 1
602             for field in todo:
603                 newfd = self.pool.get(fields_def[field]['relation']).fields_get(
604                         cr, uid, context=context)
605                 res = process_liness(self, datas, prefix + [field], newfd, position)
606                 (newrow, max2, w2, translate2, data_id2) = res
607                 nbrmax = max(nbrmax, max2)
608                 warning = warning + w2
609                 reduce(lambda x, y: x and y, newrow)
610                 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
611                         [(0, 0, newrow)]) or []
612                 i = max2
613                 while (position+i)<len(datas):
614                     ok = True
615                     for j in range(len(fields)):
616                         field2 = fields[j]
617                         if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
618                             ok = False
619                     if not ok:
620                         break
621
622                     (newrow, max2, w2, translate2, data_id2) = process_liness(
623                             self, datas, prefix+[field], newfd, position+i)
624                     warning = warning+w2
625                     if reduce(lambda x, y: x or y, newrow.values()):
626                         row[field].append((0, 0, newrow))
627                     i += max2
628                     nbrmax = max(nbrmax, i)
629
630             if len(prefix)==0:
631                 for i in range(max(nbrmax, 1)):
632                     #if datas:
633                     datas.pop(0)
634             result = (row, nbrmax, warning, translate, data_id)
635             return result
636
637         fields_def = self.fields_get(cr, uid, context=context)
638         done = 0
639
640         initial_size = len(datas)
641         if config.get('import_partial', False) and filename:
642             data = pickle.load(file(config.get('import_partial')))
643             original_value =  data.get(filename, 0)
644         counter = 0
645         while len(datas):
646             counter += 1
647             res = {}
648             #try:
649             (res, other, warning, translate, data_id) = \
650                     process_liness(self, datas, [], fields_def)
651             if warning:
652                 cr.rollback()
653                 return (-1, res, warning, '')
654             id = self.pool.get('ir.model.data')._update(cr, uid, self._name,
655                     current_module, res, xml_id=data_id, mode=mode,
656                     noupdate=noupdate)
657             for lang in translate:
658                 context2 = context.copy()
659                 context2['lang'] = lang
660                 self.write(cr, uid, [id], translate[lang], context2)
661             if config.get('import_partial', False) and filename and (not (counter%100)) :
662                 data = pickle.load(file(config.get('import_partial')))
663                 data[filename] = initial_size - len(datas) + original_value
664                 pickle.dump(data, file(config.get('import_partial'),'wb'))
665                 cr.commit()
666
667             #except Exception, e:
668             #    logger.notifyChannel("import", netsvc.LOG_ERROR, e)
669             #    cr.rollback()
670             #    try:
671             #        return (-1, res, e[0], warning)
672             #    except:
673             #        return (-1, res, e[0], '')
674             done += 1
675         #
676         # TODO: Send a request with the result and multi-thread !
677         #
678         return (done, 0, 0, 0)
679
680     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
681         raise _('The read method is not implemented on this object !')
682
683     def get_invalid_fields(self,cr,uid):
684         return list(self._invalids)
685
686     def _validate(self, cr, uid, ids, context=None):
687         context = context or {}
688         lng = context.get('lang', False) or 'en_US'
689         trans = self.pool.get('ir.translation')
690         error_msgs = []
691         for constraint in self._constraints:
692             fun, msg, fields = constraint
693             if not fun(self, cr, uid, ids):
694                 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
695                 error_msgs.append(
696                         _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
697                 )
698                 self._invalids.update(fields)
699         if error_msgs:
700             cr.rollback()
701             raise except_orm('ValidateError', '\n'.join(error_msgs))
702         else:
703             self._invalids.clear()
704
705     def default_get(self, cr, uid, fields_list, context=None):
706         return {}
707
708     def perm_read(self, cr, user, ids, context=None, details=True):
709         raise _('The perm_read method is not implemented on this object !')
710
711     def unlink(self, cr, uid, ids, context=None):
712         raise _('The unlink method is not implemented on this object !')
713
714     def write(self, cr, user, ids, vals, context=None):
715         raise _('The write method is not implemented on this object !')
716
717     def create(self, cr, user, vals, context=None):
718         raise _('The create method is not implemented on this object !')
719
720     # returns the definition of each field in the object
721     # the optional fields parameter can limit the result to some fields
722     def fields_get_keys(self, cr, user, context=None, read_access=True):
723         if context is None:
724             context = {}
725         res = self._columns.keys()
726         for parent in self._inherits:
727             res.extend(self.pool.get(parent).fields_get_keys(cr, user, fields, context))
728         return res
729
730     def fields_get(self, cr, user, fields=None, context=None, read_access=True):
731         if context is None:
732             context = {}
733         res = {}
734         translation_obj = self.pool.get('ir.translation')
735         model_access_obj = self.pool.get('ir.model.access')
736         for parent in self._inherits:
737             res.update(self.pool.get(parent).fields_get(cr, user, fields, context))
738
739         if self._columns.keys():
740             for f in self._columns.keys():
741                 if fields and f not in fields:
742                     continue
743                 res[f] = {'type': self._columns[f]._type}
744                 for arg in ('string', 'readonly', 'states', 'size', 'required',
745                         'change_default', 'translate', 'help', 'select'):
746                     if getattr(self._columns[f], arg):
747                         res[f][arg] = getattr(self._columns[f], arg)
748                 if not read_access:
749                     res[f]['readonly'] = True
750                     res[f]['states'] = {}
751                 for arg in ('digits', 'invisible','filters'):
752                     if hasattr(self._columns[f], arg) \
753                             and getattr(self._columns[f], arg):
754                         res[f][arg] = getattr(self._columns[f], arg)
755
756                 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
757                 if res_trans:
758                     res[f]['string'] = res_trans
759                 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
760                 if help_trans:
761                     res[f]['help'] = help_trans
762
763                 if hasattr(self._columns[f], 'selection'):
764                     if isinstance(self._columns[f].selection, (tuple, list)):
765                         sel = self._columns[f].selection
766                         # translate each selection option
767                         sel2 = []
768                         for (key, val) in sel:
769                             val2 = None
770                             if val:
771                                 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
772                             sel2.append((key, val2 or val))
773                         sel = sel2
774                         res[f]['selection'] = sel
775                     else:
776                         # call the 'dynamic selection' function
777                         res[f]['selection'] = self._columns[f].selection(self, cr,
778                                 user, context)
779                 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
780                     res[f]['relation'] = self._columns[f]._obj
781                     res[f]['domain'] = self._columns[f]._domain
782                     res[f]['context'] = self._columns[f]._context
783         else:
784             #TODO : read the fields from the database
785             pass
786
787         if fields:
788             # filter out fields which aren't in the fields list
789             for r in res.keys():
790                 if r not in fields:
791                     del res[r]
792         return res
793
794     #
795     # Overload this method if you need a window title which depends on the context
796     #
797     def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
798         return False
799
800     def __view_look_dom(self, cr, user, node, view_id, context=None):
801         if not context:
802             context = {}
803         result = False
804         fields = {}
805         childs = True
806
807         if node.nodeType == node.ELEMENT_NODE and node.localName == 'field':
808             if node.hasAttribute('name'):
809                 attrs = {}
810                 try:
811                     if node.getAttribute('name') in self._columns:
812                         column = self._columns[node.getAttribute('name')]
813                     else:
814                         column = self._inherit_fields[node.getAttribute('name')][2]
815                 except:
816                     column = False
817
818                 if column:
819                     relation = column._obj
820                     childs = False
821                     views = {}
822                     for f in node.childNodes:
823                         if f.nodeType == f.ELEMENT_NODE and f.localName in ('form', 'tree', 'graph'):
824                             node.removeChild(f)
825                             ctx = context.copy()
826                             ctx['base_model_name'] = self._name
827                             xarch, xfields = self.pool.get(relation).__view_look_dom_arch(cr, user, f, view_id, ctx)
828                             views[str(f.localName)] = {
829                                 'arch': xarch,
830                                 'fields': xfields
831                             }
832                     attrs = {'views': views}
833                     if node.hasAttribute('widget') and node.getAttribute('widget')=='selection':
834                         # We can not use the 'string' domain has it is defined according to the record !
835                         dom = None
836                         if column._domain and not isinstance(column._domain, (str, unicode)):
837                             dom = column._domain
838                         attrs['selection'] = self.pool.get(relation).name_search(cr, user, '', dom, context=context)
839                         if (node.hasAttribute('required') and not int(node.getAttribute('required'))) or not column.required:
840                             attrs['selection'].append((False,''))
841                 fields[node.getAttribute('name')] = attrs
842
843         elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form', 'tree'):
844             result = self.view_header_get(cr, user, False, node.localName, context)
845             if result:
846                 node.setAttribute('string', result)
847
848         elif node.nodeType==node.ELEMENT_NODE and node.localName == 'calendar':
849             for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
850                 if node.hasAttribute(additional_field) and node.getAttribute(additional_field):
851                     fields[node.getAttribute(additional_field)] = {}
852
853         if node.nodeType == node.ELEMENT_NODE and node.hasAttribute('groups'):
854             if node.getAttribute('groups'):
855                 groups = node.getAttribute('groups').split(',')
856                 readonly = False
857                 access_pool = self.pool.get('ir.model.access')
858                 for group in groups:
859                     readonly = readonly or access_pool.check_groups(cr, user, group)
860                 if not readonly:
861                     node.setAttribute('invisible', '1')
862             node.removeAttribute('groups')
863
864         if node.nodeType == node.ELEMENT_NODE:
865             # translate view
866             if ('lang' in context) and not result:
867                 if node.hasAttribute('string') and node.getAttribute('string'):
868                     trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.getAttribute('string').encode('utf8'))
869                     if not trans and ('base_model_name' in context):
870                         trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.getAttribute('string').encode('utf8'))
871                     if trans:
872                         node.setAttribute('string', trans)
873                 if node.hasAttribute('sum') and node.getAttribute('sum'):
874                     trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.getAttribute('sum').encode('utf8'))
875                     if trans:
876                         node.setAttribute('sum', trans)
877
878         if childs:
879             for f in node.childNodes:
880                 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
881
882         return fields
883
884     def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
885         fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
886
887         rolesobj = self.pool.get('res.roles')
888         usersobj = self.pool.get('res.users')
889
890         buttons = (n for n in node.getElementsByTagName('button') if n.getAttribute('type') != 'object')
891         for button in buttons:
892             ok = True
893             if user != 1:   # admin user has all roles
894                 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
895                 cr.execute("select role_id from wkf_transition where signal=%s", (button.getAttribute('name'),))
896                 roles = cr.fetchall()
897                 for role in roles:
898                     if role[0]:
899                         ok = ok and rolesobj.check(cr, user, user_roles, role[0])
900
901             if not ok:
902                 button.setAttribute('readonly', '1')
903             else:
904                 button.setAttribute('readonly', '0')
905
906         arch = node.toxml(encoding="utf-8").replace('\t', '')
907         fields = self.fields_get(cr, user, fields_def.keys(), context)
908         for field in fields_def:
909             if field == 'id':
910                 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
911                 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
912             elif field in fields:
913                 fields[field].update(fields_def[field])
914             else:
915                 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))
916                 res = cr.fetchall()[:]
917                 model = res[0][1]
918                 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
919                 msg = "\n * ".join([r[0] for r in res])
920                 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
921                 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
922                 raise except_orm('View error', msg)
923
924         return arch, fields
925
926     def __get_default_calendar_view(self):
927         """Generate a default calendar view (For internal use only).
928         """
929
930         arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
931                 '<calendar string="%s" date_start="%s"') % (self._description, self._date_name)
932
933         if 'user_id' in self._columns:
934             arch += ' color="user_id"'
935
936         elif 'partner_id' in self._columns:
937             arch += ' color="partner_id"'
938
939         if 'date_stop' in self._columns:
940             arch += ' date_stop="date_stop"'
941
942         elif 'date_end' in self._columns:
943             arch += ' date_stop="date_end"'
944
945         elif 'date_delay' in self._columns:
946             arch += ' date_delay="date_delay"'
947
948         elif 'planned_hours' in self._columns:
949             arch += ' date_delay="planned_hours"'
950
951         arch += ('>\n'
952                  '  <field name="%s"/>\n'
953                  '</calendar>') % (self._rec_name)
954
955         return arch
956
957     #
958     # if view_id, view_type is not required
959     #
960     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False):
961         if not context:
962             context = {}
963
964         def encode(s):
965             if isinstance(s, unicode):
966                 return s.encode('utf8')
967             return s
968
969         def _inherit_apply(src, inherit):
970             def _find(node, node2):
971                 if node2.nodeType == node2.ELEMENT_NODE and node2.localName == 'xpath':
972                     res = xpath.Evaluate(node2.getAttribute('expr'), node)
973                     return res and res[0]
974                 else:
975                     if node.nodeType == node.ELEMENT_NODE and node.localName == node2.localName:
976                         res = True
977                         for attr in node2.attributes.keys():
978                             if attr == 'position':
979                                 continue
980                             if node.hasAttribute(attr):
981                                 if node.getAttribute(attr)==node2.getAttribute(attr):
982                                     continue
983                             res = False
984                         if res:
985                             return node
986                     for child in node.childNodes:
987                         res = _find(child, node2)
988                         if res:
989                             return res
990                 return None
991
992
993             doc_src = dom.minidom.parseString(encode(src))
994             doc_dest = dom.minidom.parseString(encode(inherit))
995             toparse = doc_dest.childNodes
996             while len(toparse):
997                 node2 = toparse.pop(0)
998                 if not node2.nodeType == node2.ELEMENT_NODE:
999                     continue
1000                 if node2.localName == 'data':
1001                     toparse += node2.childNodes
1002                     continue
1003                 node = _find(doc_src, node2)
1004                 if node:
1005                     pos = 'inside'
1006                     if node2.hasAttribute('position'):
1007                         pos = node2.getAttribute('position')
1008                     if pos == 'replace':
1009                         parent = node.parentNode
1010                         for child in node2.childNodes:
1011                             if child.nodeType == child.ELEMENT_NODE:
1012                                 parent.insertBefore(child, node)
1013                         parent.removeChild(node)
1014                     else:
1015                         sib = node.nextSibling
1016                         for child in node2.childNodes:
1017                             if child.nodeType == child.ELEMENT_NODE:
1018                                 if pos == 'inside':
1019                                     node.appendChild(child)
1020                                 elif pos == 'after':
1021                                     node.parentNode.insertBefore(child, sib)
1022                                 elif pos=='before':
1023                                     node.parentNode.insertBefore(child, node)
1024                                 else:
1025                                     raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1026                 else:
1027                     attrs = ''.join([
1028                         ' %s="%s"' % (attr, node2.getAttribute(attr))
1029                         for attr in node2.attributes.keys()
1030                         if attr != 'position'
1031                     ])
1032                     tag = "<%s%s>" % (node2.localName, attrs)
1033                     raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1034             return doc_src.toxml(encoding="utf-8").replace('\t', '')
1035
1036         result = {'type': view_type, 'model': self._name}
1037
1038         ok = True
1039         model = True
1040         sql_res = False
1041         while ok:
1042             if view_id:
1043                 where = (model and (" and model='%s'" % (self._name,))) or ''
1044                 cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s'+where, (view_id,))
1045             else:
1046                 cr.execute('''SELECT
1047                         arch,name,field_parent,id,type,inherit_id
1048                     FROM
1049                         ir_ui_view
1050                     WHERE
1051                         model=%s AND
1052                         type=%s AND
1053                         inherit_id IS NULL
1054                     ORDER BY priority''', (self._name, view_type))
1055             sql_res = cr.fetchone()
1056             if not sql_res:
1057                 break
1058             ok = sql_res[5]
1059             view_id = ok or sql_res[3]
1060             model = False
1061
1062         # if a view was found
1063         if sql_res:
1064             result['type'] = sql_res[4]
1065             result['view_id'] = sql_res[3]
1066             result['arch'] = sql_res[0]
1067
1068             def _inherit_apply_rec(result, inherit_id):
1069                 # get all views which inherit from (ie modify) this view
1070                 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1071                 sql_inherit = cr.fetchall()
1072                 for (inherit, id) in sql_inherit:
1073                     result = _inherit_apply(result, inherit)
1074                     result = _inherit_apply_rec(result, id)
1075                 return result
1076
1077             result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
1078
1079             result['name'] = sql_res[1]
1080             result['field_parent'] = sql_res[2] or False
1081         else:
1082             # otherwise, build some kind of default view
1083             if view_type == 'form':
1084                 res = self.fields_get(cr, user, context=context)
1085                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1086                 '''<form string="%s">''' % (self._description,)
1087                 for x in res:
1088                     if res[x]['type'] not in ('one2many', 'many2many'):
1089                         xml += '<field name="%s"/>' % (x,)
1090                         if res[x]['type'] == 'text':
1091                             xml += "<newline/>"
1092                 xml += "</form>"
1093             elif view_type == 'tree':
1094                 _rec_name = self._rec_name
1095                 if _rec_name not in self._columns:
1096                     _rec_name = self._columns.keys()[0]
1097                 xml = '''<?xml version="1.0" encoding="utf-8"?>''' \
1098                 '''<tree string="%s"><field name="%s"/></tree>''' \
1099                 % (self._description, self._rec_name)
1100             elif view_type == 'calendar':
1101                 xml = self.__get_default_calendar_view()
1102             else:
1103                 xml = ''
1104             result['arch'] = xml
1105             result['name'] = 'default'
1106             result['field_parent'] = False
1107             result['view_id'] = 0
1108
1109         doc = dom.minidom.parseString(encode(result['arch']))
1110         xarch, xfields = self.__view_look_dom_arch(cr, user, doc, view_id, context=context)
1111         result['arch'] = xarch
1112         result['fields'] = xfields
1113         if toolbar:
1114             def clean(x):
1115                 x = x[2]
1116                 for key in ('report_sxw_content', 'report_rml_content',
1117                         'report_sxw', 'report_rml',
1118                         'report_sxw_content_data', 'report_rml_content_data'):
1119                     if key in x:
1120                         del x[key]
1121                 return x
1122             ir_values_obj = self.pool.get('ir.values')
1123             resprint = ir_values_obj.get(cr, user, 'action',
1124                     'client_print_multi', [(self._name, False)], False,
1125                     context)
1126             resaction = ir_values_obj.get(cr, user, 'action',
1127                     'client_action_multi', [(self._name, False)], False,
1128                     context)
1129
1130             resrelate = ir_values_obj.get(cr, user, 'action',
1131                     'client_action_relate', [(self._name, False)], False,
1132                     context)
1133             resprint = map(clean, resprint)
1134             resaction = map(clean, resaction)
1135             resaction = filter(lambda x: not x.get('multi', False), resaction)
1136             resprint = filter(lambda x: not x.get('multi', False), resprint)
1137             resrelate = map(lambda x: x[2], resrelate)
1138
1139             for x in resprint+resaction+resrelate:
1140                 x['string'] = x['name']
1141
1142             result['toolbar'] = {
1143                 'print': resprint,
1144                 'action': resaction,
1145                 'relate': resrelate
1146             }
1147         return result
1148
1149     _view_look_dom_arch = __view_look_dom_arch
1150
1151     def search_count(self, cr, user, args, context=None):
1152         if not context:
1153             context = {}
1154         res = self.search(cr, user, args, context=context, count=True)
1155         if isinstance(res, list):
1156             return len(res)
1157         return res
1158
1159     def search(self, cr, user, args, offset=0, limit=None, order=None,
1160             context=None, count=False):
1161         raise _('The search method is not implemented on this object !')
1162
1163     def name_get(self, cr, user, ids, context=None):
1164         raise _('The name_get method is not implemented on this object !')
1165
1166     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=None):
1167         raise _('The name_search method is not implemented on this object !')
1168
1169     def copy(self, cr, uid, id, default=None, context=None):
1170         raise _('The copy method is not implemented on this object !')
1171
1172     def exists(self, cr, uid, id, context=None):
1173         raise _('The exists method is not implemented on this object !')
1174
1175     def read_string(self, cr, uid, id, langs, fields=None, context=None):
1176         res = {}
1177         res2 = {}
1178         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read')
1179         if not fields:
1180             fields = self._columns.keys() + self._inherit_fields.keys()
1181         for lang in langs:
1182             res[lang] = {'code': lang}
1183             for f in fields:
1184                 if f in self._columns:
1185                     res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1186                     if res_trans:
1187                         res[lang][f] = res_trans
1188                     else:
1189                         res[lang][f] = self._columns[f].string
1190         for table in self._inherits:
1191             cols = intersect(self._inherit_fields.keys(), fields)
1192             res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1193         for lang in res2:
1194             if lang in res:
1195                 res[lang]['code'] = lang
1196             for f in res2[lang]:
1197                 res[lang][f] = res2[lang][f]
1198         return res
1199
1200     def write_string(self, cr, uid, id, langs, vals, context=None):
1201         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write')
1202         for lang in langs:
1203             for field in vals:
1204                 if field in self._columns:
1205                     self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field])
1206         for table in self._inherits:
1207             cols = intersect(self._inherit_fields.keys(), vals)
1208             if cols:
1209                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1210         return True
1211
1212     def _check_removed_columns(self, cr, log=False):
1213         raise NotImplementedError()
1214
1215 class orm_memory(orm_template):
1216     _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']
1217     _inherit_fields = {}
1218     _max_count = 200
1219     _max_hours = 1
1220     _check_time = 20
1221
1222     def __init__(self, cr):
1223         super(orm_memory, self).__init__(cr)
1224         self.datas = {}
1225         self.next_id = 0
1226         self.check_id = 0
1227         cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1228
1229     def vaccum(self, cr, uid):
1230         self.check_id += 1
1231         if self.check_id % self._check_time:
1232             return True
1233         tounlink = []
1234         max = time.time() - self._max_hours * 60 * 60
1235         for id in self.datas:
1236             if self.datas[id]['internal.date_access'] < max:
1237                 tounlink.append(id)
1238         self.unlink(cr, uid, tounlink)
1239         if len(self.datas)>self._max_count:
1240             sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1241             sorted.sort()
1242             ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1243             self.unlink(cr, uid, ids)
1244         return True
1245
1246     def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1247         if not context:
1248             context = {}
1249         if not fields_to_read:
1250             fields_to_read = self._columns.keys()
1251         result = []
1252         if self.datas:
1253             if isinstance(ids, (int, long)):
1254                 ids = [ids]
1255             for id in ids:
1256                 r = {'id': id}
1257                 for f in fields_to_read:
1258                     if id in self.datas:
1259                         r[f] = self.datas[id].get(f, False)
1260                         if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1261                             r[f] = len(r[f])
1262                 result.append(r)
1263                 if id in self.datas:
1264                     self.datas[id]['internal.date_access'] = time.time()
1265             fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1266             for f in fields_post:
1267                 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1268                 for record in result:
1269                     record[f] = res2[record['id']]
1270             if isinstance(ids, (int, long)):
1271                 return result[0]
1272         return result
1273
1274     def write(self, cr, user, ids, vals, context=None):
1275         vals2 = {}
1276         upd_todo = []
1277         for field in vals:
1278             if self._columns[field]._classic_write:
1279                 vals2[field] = vals[field]
1280             else:
1281                 upd_todo.append(field)
1282         for id_new in ids:
1283             self.datas[id_new].update(vals2)
1284             self.datas[id_new]['internal.date_access'] = time.time()
1285             for field in upd_todo:
1286                 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1287         self._validate(cr, user, [id_new], context)
1288         wf_service = netsvc.LocalService("workflow")
1289         wf_service.trg_write(user, self._name, id_new, cr)
1290         self.vaccum(cr, user)
1291         return id_new
1292
1293     def create(self, cr, user, vals, context=None):
1294         self.next_id += 1
1295         id_new = self.next_id
1296         default = []
1297         for f in self._columns.keys():
1298             if not f in vals:
1299                 default.append(f)
1300         if len(default):
1301             vals.update(self.default_get(cr, user, default, context))
1302         vals2 = {}
1303         upd_todo = []
1304         for field in vals:
1305             if self._columns[field]._classic_write:
1306                 vals2[field] = vals[field]
1307             else:
1308                 upd_todo.append(field)
1309         self.datas[id_new] = vals2
1310         self.datas[id_new]['internal.date_access'] = time.time()
1311
1312         for field in upd_todo:
1313             self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1314         self._validate(cr, user, [id_new], context)
1315         wf_service = netsvc.LocalService("workflow")
1316         wf_service.trg_create(user, self._name, id_new, cr)
1317         self.vaccum(cr, user)
1318         return id_new
1319
1320     def default_get(self, cr, uid, fields_list, context=None):
1321         if not context:
1322             context = {}
1323         value = {}
1324         # get the default values for the inherited fields
1325         for f in fields_list:
1326             if f in self._defaults:
1327                 value[f] = self._defaults[f](self, cr, uid, context)
1328             fld_def = ((f in self._columns) and self._columns[f]) \
1329                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1330                     or False
1331
1332         # get the default values set by the user and override the default
1333         # values defined in the object
1334         ir_values_obj = self.pool.get('ir.values')
1335         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1336         for id, field, field_value in res:
1337             if field in fields_list:
1338                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1339                 if fld_def._type in ('many2one', 'one2one'):
1340                     obj = self.pool.get(fld_def._obj)
1341                     if not obj.search(cr, uid, [('id', '=', field_value)]):
1342                         continue
1343                 if fld_def._type in ('many2many'):
1344                     obj = self.pool.get(fld_def._obj)
1345                     field_value2 = []
1346                     for i in range(len(field_value)):
1347                         if not obj.search(cr, uid, [('id', '=',
1348                             field_value[i])]):
1349                             continue
1350                         field_value2.append(field_value[i])
1351                     field_value = field_value2
1352                 if fld_def._type in ('one2many'):
1353                     obj = self.pool.get(fld_def._obj)
1354                     field_value2 = []
1355                     for i in range(len(field_value)):
1356                         field_value2.append({})
1357                         for field2 in field_value[i]:
1358                             if obj._columns[field2]._type in ('many2one', 'one2one'):
1359                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1360                                 if not obj2.search(cr, uid,
1361                                         [('id', '=', field_value[i][field2])]):
1362                                     continue
1363                             # TODO add test for many2many and one2many
1364                             field_value2[i][field2] = field_value[i][field2]
1365                     field_value = field_value2
1366                 value[field] = field_value
1367
1368         # get the default values from the context
1369         for key in context or {}:
1370             if key.startswith('default_'):
1371                 value[key[8:]] = context[key]
1372         return value
1373
1374     def search(self, cr, user, args, offset=0, limit=None, order=None,
1375             context=None, count=False):
1376         return self.datas.keys()
1377
1378     def unlink(self, cr, uid, ids, context=None):
1379         for id in ids:
1380             if id in self.datas:
1381                 del self.datas[id]
1382         if len(ids):
1383             cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str, ids))+')', (self._name, ))
1384         return True
1385
1386     def perm_read(self, cr, user, ids, context=None, details=True):
1387         result = []
1388         for id in ids:
1389             result.append({
1390                 'create_uid': (user, 'Root'),
1391                 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'),
1392                 'write_uid': False,
1393                 'write_date': False,
1394                 'id': id
1395             })
1396         return result
1397
1398     def _check_removed_columns(self, cr, log=False):
1399         # nothing to check in memory...
1400         pass
1401     
1402     def exists(self, cr, uid, id, context=None):
1403         return id in self.datas
1404
1405 class orm(orm_template):
1406     _sql_constraints = []
1407     _table = None
1408     _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']
1409
1410     def _parent_store_compute(self, cr):
1411         logger = netsvc.Logger()
1412         logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
1413         def browse_rec(root, pos=0):
1414 # TODO: set order
1415             where = self._parent_name+'='+str(root)
1416             if not root:
1417                 where = self._parent_name+' IS NULL'
1418             if self._parent_order:
1419                 where += ' order by '+self._parent_order
1420             cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
1421             pos2 = pos + 1
1422             childs = cr.fetchall()
1423             for id in childs:
1424                 pos2 = browse_rec(id[0], pos2)
1425             cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
1426             return pos2+1
1427         query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
1428         if self._parent_order:
1429             query += ' order by '+self._parent_order
1430         pos = 0
1431         cr.execute(query)
1432         for (root,) in cr.fetchall():
1433             pos = browse_rec(root, pos)
1434         return True
1435
1436     def _update_store(self, cr, f, k):
1437         logger = netsvc.Logger()
1438         logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
1439         ss = self._columns[k]._symbol_set
1440         update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
1441         cr.execute('select id from '+self._table)
1442         ids_lst = map(lambda x: x[0], cr.fetchall())
1443         while ids_lst:
1444             iids = ids_lst[:40]
1445             ids_lst = ids_lst[40:]
1446             res = f.get(cr, self, iids, k, 1, {})
1447             for key,val in res.items():
1448                 if f._multi:
1449                     val = val[k]
1450                 # if val is a many2one, just write the ID
1451                 if type(val)==tuple:
1452                     val = val[0]
1453                 if (val<>False) or (type(val)<>bool):
1454                     cr.execute(update_query, (ss[1](val), key))
1455
1456     def _check_removed_columns(self, cr, log=False):
1457         logger = netsvc.Logger()
1458         # iterate on the database columns to drop the NOT NULL constraints
1459         # of fields which were required but have been removed (or will be added by another module)
1460         columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
1461         columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
1462         cr.execute("SELECT a.attname, a.attnotnull"
1463                    "  FROM pg_class c, pg_attribute a"
1464                    " WHERE c.relname=%%s"
1465                    "   AND c.oid=a.attrelid"
1466                    "   AND a.attisdropped=%%s"
1467                    "   AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
1468                    "   AND a.attname NOT IN (%s)" % ",".join(['%s']*len(columns)),
1469                        [self._table, False] + columns)
1470         for column in cr.dictfetchall():
1471             if log:
1472                 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))
1473             if column['attnotnull']:
1474                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
1475
1476     def _auto_init(self, cr, context={}):
1477         store_compute =  False
1478         logger = netsvc.Logger()
1479         create = False
1480         todo_end = []
1481         self._field_create(cr, context=context)
1482         if not hasattr(self, "_auto") or self._auto:
1483             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
1484             if not cr.rowcount:
1485                 cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table)
1486                 create = True
1487             cr.commit()
1488             if self._parent_store:
1489                 cr.execute("""SELECT c.relname
1490                     FROM pg_class c, pg_attribute a
1491                     WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
1492                     """, (self._table, 'parent_left'))
1493                 if not cr.rowcount:
1494                     if 'parent_left' not in self._columns:
1495                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
1496                     if 'parent_right' not in self._columns:
1497                         logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
1498                     if self._columns[self._parent_name].ondelete<>'cascade':
1499                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "the columns %s on object must be set as ondelete='cascasde'" % (self._name, self._parent_name))
1500                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
1501                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
1502                     cr.commit()
1503                     store_compute = True
1504
1505             if self._log_access:
1506                 logs = {
1507                     'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1508                     'create_date': 'TIMESTAMP',
1509                     'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
1510                     'write_date': 'TIMESTAMP'
1511                 }
1512                 for k in logs:
1513                     cr.execute("""
1514                         SELECT c.relname
1515                           FROM pg_class c, pg_attribute a
1516                          WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
1517                         """, (self._table, k))
1518                     if not cr.rowcount:
1519                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
1520                         cr.commit()
1521
1522             self._check_removed_columns(cr, log=False)
1523
1524             # iterate on the "object columns"
1525             todo_update_store = []
1526             for k in self._columns:
1527                 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
1528                     continue
1529                     #raise _('Can not define a column %s. Reserved keyword !') % (k,)
1530                 f = self._columns[k]
1531
1532                 if isinstance(f, fields.one2many):
1533                     cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
1534                     if cr.fetchone():
1535                         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))
1536                         res = cr.fetchone()[0]
1537                         if not res:
1538                             cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
1539                 elif isinstance(f, fields.many2many):
1540                     cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
1541                     if not cr.dictfetchall():
1542                         #FIXME: Remove this try/except
1543                         try:
1544                             ref = self.pool.get(f._obj)._table
1545                         except AttributeError:
1546                             ref = f._obj.replace('.', '_')
1547                         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))
1548                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
1549                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
1550                         cr.commit()
1551                 else:
1552                     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 " \
1553                                "FROM pg_class c,pg_attribute a,pg_type t " \
1554                                "WHERE c.relname=%s " \
1555                                "AND a.attname=%s " \
1556                                "AND c.oid=a.attrelid " \
1557                                "AND a.atttypid=t.oid", (self._table, k))
1558                     res = cr.dictfetchall()
1559                     if not res:
1560                         if not isinstance(f, fields.function) or f.store:
1561
1562                             # add the missing field
1563                             cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
1564
1565                             # initialize it
1566                             if not create and k in self._defaults:
1567                                 default = self._defaults[k](self, cr, 1, {})
1568                                 ss = self._columns[k]._symbol_set
1569                                 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
1570                                 cr.execute(query, (ss[1](default),))
1571                                 cr.commit()
1572                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
1573                             elif not create:
1574                                 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
1575
1576                             if isinstance(f, fields.function):
1577                                 order = 10
1578                                 if f.store is not True:
1579                                     order = f.store[f.store.keys()[0]][2]
1580                                 todo_update_store.append((order, f,k))
1581
1582                             # and add constraints if needed
1583                             if isinstance(f, fields.many2one):
1584                                 #FIXME: Remove this try/except
1585                                 try:
1586                                     ref = self.pool.get(f._obj)._table
1587                                 except AttributeError:
1588                                     ref = f._obj.replace('.', '_')
1589                                 # ir_actions is inherited so foreign key doesn't work on it
1590                                 if ref != 'ir_actions':
1591                                     cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
1592                             if f.select:
1593                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
1594                             if f.required:
1595                                 try:
1596                                     cr.commit()
1597                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
1598                                 except Exception, e:
1599                                     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))
1600                             cr.commit()
1601                     elif len(res)==1:
1602                         f_pg_def = res[0]
1603                         f_pg_type = f_pg_def['typname']
1604                         f_pg_size = f_pg_def['size']
1605                         f_pg_notnull = f_pg_def['attnotnull']
1606                         if isinstance(f, fields.function) and not f.store:
1607                             logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
1608                             cr.execute('ALTER TABLE %s DROP COLUMN %s'% (self._table, k))
1609                             cr.commit()
1610                             f_obj_type = None
1611                         else:
1612                             f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
1613
1614                         if f_obj_type:
1615                             ok = False
1616                             casts = [
1617                                 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
1618                                 ('varchar', 'text', 'TEXT', ''),
1619                                 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
1620                                 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
1621                             ]
1622                             if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
1623                                 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
1624                                 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
1625                                 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
1626                                 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
1627                                 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size' % (self._table,))
1628                                 cr.commit()
1629                             for c in casts:
1630                                 if (f_pg_type==c[0]) and (f._type==c[1]):
1631                                     logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
1632                                     ok = True
1633                                     cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
1634                                     cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
1635                                     cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
1636                                     cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
1637                                     cr.commit()
1638
1639                             if f_pg_type != f_obj_type:
1640                                 if not ok:
1641                                     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))
1642
1643                             # if the field is required and hasn't got a NOT NULL constraint
1644                             if f.required and f_pg_notnull == 0:
1645                                 # set the field to the default value if any
1646                                 if k in self._defaults:
1647                                     default = self._defaults[k](self, cr, 1, {})
1648                                     if (default is not None):
1649                                         ss = self._columns[k]._symbol_set
1650                                         query = 'UPDATE "%s" SET "%s"=%s WHERE %s is NULL' % (self._table, k, ss[0], k)
1651                                         cr.execute(query, (ss[1](default),))
1652                                 # add the NOT NULL constraint
1653                                 cr.commit()
1654                                 try:
1655                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
1656                                     cr.commit()
1657                                 except Exception, e:
1658                                     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))
1659                                 cr.commit()
1660                             elif not f.required and f_pg_notnull == 1:
1661                                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
1662                                 cr.commit()
1663                             indexname = '%s_%s_index' % (self._table, k)
1664                             cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
1665                             res = cr.dictfetchall()
1666                             if not res and f.select:
1667                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
1668                                 cr.commit()
1669                             if res and not f.select:
1670                                 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
1671                                 cr.commit()
1672                             if isinstance(f, fields.many2one):
1673                                 ref = self.pool.get(f._obj)._table
1674                                 if ref != 'ir_actions':
1675                                     cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
1676                                                 'pg_attribute as att1, pg_attribute as att2 '
1677                                             'WHERE con.conrelid = cl1.oid '
1678                                                 'AND cl1.relname = %s '
1679                                                 'AND con.confrelid = cl2.oid '
1680                                                 'AND cl2.relname = %s '
1681                                                 'AND array_lower(con.conkey, 1) = 1 '
1682                                                 'AND con.conkey[1] = att1.attnum '
1683                                                 'AND att1.attrelid = cl1.oid '
1684                                                 'AND att1.attname = %s '
1685                                                 'AND array_lower(con.confkey, 1) = 1 '
1686                                                 'AND con.confkey[1] = att2.attnum '
1687                                                 'AND att2.attrelid = cl2.oid '
1688                                                 'AND att2.attname = %s '
1689                                                 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
1690                                     res = cr.dictfetchall()
1691                                     if res:
1692                                         confdeltype = {
1693                                             'RESTRICT': 'r',
1694                                             'NO ACTION': 'a',
1695                                             'CASCADE': 'c',
1696                                             'SET NULL': 'n',
1697                                             'SET DEFAULT': 'd',
1698                                         }
1699                                         if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'):
1700                                             cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
1701                                             cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
1702                                             cr.commit()
1703                     else:
1704                         logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error !")
1705             for order,f,k in todo_update_store:
1706                 todo_end.append((order, self._update_store, (f, k)))
1707
1708         else:
1709             cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (self._table,))
1710             create = not bool(cr.fetchone())
1711
1712         for (key, con, _) in self._sql_constraints:
1713             conname = '%s_%s' % (self._table, key)
1714             cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
1715             if not cr.dictfetchall():
1716                 try:
1717                     cr.execute('alter table "%s" add constraint "%s_%s" %s' % (self._table, self._table, key, con,))
1718                     cr.commit()
1719                 except:
1720                     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,))
1721
1722         if create:
1723             if hasattr(self, "_sql"):
1724                 for line in self._sql.split(';'):
1725                     line2 = line.replace('\n', '').strip()
1726                     if line2:
1727                         cr.execute(line2)
1728                         cr.commit()
1729         if store_compute:
1730             self._parent_store_compute(cr)
1731         return todo_end
1732
1733     def __init__(self, cr):
1734         super(orm, self).__init__(cr)
1735
1736         if not hasattr(self, '_log_access'):
1737             # if not access is not specify, it is the same value as _auto
1738             self._log_access = not hasattr(self, "_auto") or self._auto
1739
1740         self._columns = self._columns.copy()
1741         for store_field in self._columns:
1742             f = self._columns[store_field]
1743             if not isinstance(f, fields.function):
1744                 continue
1745             if not f.store:
1746                 continue
1747             if self._columns[store_field].store is True:
1748                 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10)}
1749             else:
1750                 sm = self._columns[store_field].store
1751             for object, aa in sm.items():
1752                 if len(aa)==3:
1753                     (fnct,fields2,order)=aa
1754                 else:
1755                     raise except_orm('Error',
1756                         ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority)}.' % (store_field, self._name)))
1757                 self.pool._store_function.setdefault(object, [])
1758                 ok = True
1759                 for x,y,z,e,f in self.pool._store_function[object]:
1760                     if (x==self._name) and (y==store_field) and (e==fields2):
1761                         ok = False
1762                 if ok:
1763                     self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order))
1764                     self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
1765
1766         for (key, _, msg) in self._sql_constraints:
1767             self.pool._sql_error[self._table+'_'+key] = msg
1768
1769         # Load manual fields
1770
1771         cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
1772         if cr.fetchone():
1773             cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
1774             for field in cr.dictfetchall():
1775                 if field['name'] in self._columns:
1776                     continue
1777                 attrs = {
1778                     'string': field['field_description'],
1779                     'required': bool(field['required']),
1780                     'readonly': bool(field['readonly']),
1781                     'domain': field['domain'] or None,
1782                     'size': field['size'],
1783                     'ondelete': field['on_delete'],
1784                     'translate': (field['translate']),
1785                     #'select': int(field['select_level'])
1786                 }
1787
1788                 if field['ttype'] == 'selection':
1789                     self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
1790                 elif field['ttype'] == 'reference':
1791                     self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
1792                 elif field['ttype'] == 'many2one':
1793                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
1794                 elif field['ttype'] == 'one2many':
1795                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
1796                 elif field['ttype'] == 'many2many':
1797                     import random
1798                     _rel1 = field['relation'].replace('.', '_')
1799                     _rel2 = field['model'].replace('.', '_')
1800                     _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
1801                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
1802                 else:
1803                     self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
1804
1805         self._inherits_reload()
1806         if not self._sequence:
1807             self._sequence = self._table+'_id_seq'
1808         for k in self._defaults:
1809             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,)
1810         for f in self._columns:
1811             self._columns[f].restart()
1812
1813     def default_get(self, cr, uid, fields_list, context=None):
1814         if not context:
1815             context = {}
1816         value = {}
1817         # get the default values for the inherited fields
1818         for t in self._inherits.keys():
1819             value.update(self.pool.get(t).default_get(cr, uid, fields_list,
1820                 context))
1821
1822         # get the default values defined in the object
1823         for f in fields_list:
1824             if f in self._defaults:
1825                 value[f] = self._defaults[f](self, cr, uid, context)
1826             fld_def = ((f in self._columns) and self._columns[f]) \
1827                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1828                     or False
1829             if isinstance(fld_def, fields.property):
1830                 property_obj = self.pool.get('ir.property')
1831                 definition_id = fld_def._field_get(cr, uid, self._name, f)
1832                 nid = property_obj.search(cr, uid, [('fields_id', '=',
1833                     definition_id), ('res_id', '=', False)])
1834                 if nid:
1835                     prop_value = property_obj.browse(cr, uid, nid[0],
1836                             context=context).value
1837                     value[f] = (prop_value and int(prop_value.split(',')[1])) \
1838                             or False
1839
1840         # get the default values set by the user and override the default
1841         # values defined in the object
1842         ir_values_obj = self.pool.get('ir.values')
1843         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1844         for id, field, field_value in res:
1845             if field in fields_list:
1846                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1847                 if fld_def._type in ('many2one', 'one2one'):
1848                     obj = self.pool.get(fld_def._obj)
1849                     if not obj.search(cr, uid, [('id', '=', field_value)]):
1850                         continue
1851                 if fld_def._type in ('many2many'):
1852                     obj = self.pool.get(fld_def._obj)
1853                     field_value2 = []
1854                     for i in range(len(field_value)):
1855                         if not obj.search(cr, uid, [('id', '=',
1856                             field_value[i])]):
1857                             continue
1858                         field_value2.append(field_value[i])
1859                     field_value = field_value2
1860                 if fld_def._type in ('one2many'):
1861                     obj = self.pool.get(fld_def._obj)
1862                     field_value2 = []
1863                     for i in range(len(field_value)):
1864                         field_value2.append({})
1865                         for field2 in field_value[i]:
1866                             if obj._columns[field2]._type in ('many2one', 'one2one'):
1867                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1868                                 if not obj2.search(cr, uid,
1869                                         [('id', '=', field_value[i][field2])]):
1870                                     continue
1871                             # TODO add test for many2many and one2many
1872                             field_value2[i][field2] = field_value[i][field2]
1873                     field_value = field_value2
1874                 value[field] = field_value
1875         for key in context or {}:
1876             if key.startswith('default_'):
1877                 value[key[8:]] = context[key]
1878         return value
1879
1880     #
1881     # Update objects that uses this one to update their _inherits fields
1882     #
1883     def _inherits_reload_src(self):
1884         for obj in self.pool.obj_pool.values():
1885             if self._name in obj._inherits:
1886                 obj._inherits_reload()
1887
1888     def _inherits_reload(self):
1889         res = {}
1890         for table in self._inherits:
1891             res.update(self.pool.get(table)._inherit_fields)
1892             for col in self.pool.get(table)._columns.keys():
1893                 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
1894             for col in self.pool.get(table)._inherit_fields.keys():
1895                 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
1896         self._inherit_fields = res
1897         self._inherits_reload_src()
1898
1899     def fields_get(self, cr, user, fields=None, context=None):
1900         read_access = self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False)
1901         return super(orm, self).fields_get(cr, user, fields, context, read_access)
1902
1903     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1904         if not context:
1905             context = {}
1906         self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
1907         if not fields:
1908             fields = self._columns.keys() + self._inherit_fields.keys()
1909         select = ids
1910         if isinstance(ids, (int, long)):
1911             select = [ids]
1912         result = self._read_flat(cr, user, select, fields, context, load)
1913         for r in result:
1914             for key, v in r.items():
1915                 if v == None:
1916                     r[key] = False
1917         if isinstance(ids, (int, long)):
1918             return result and result[0] or False
1919         return result
1920
1921     def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
1922         if not context:
1923             context = {}
1924         if not ids:
1925             return []
1926
1927         if fields_to_read == None:
1928             fields_to_read = self._columns.keys()
1929
1930         # construct a clause for the rules :
1931         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
1932
1933         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
1934         fields_pre = [f for f in fields_to_read if
1935                            f == self.CONCURRENCY_CHECK_FIELD
1936                         or (f in self._columns and getattr(self._columns[f], '_classic_write'))
1937                      ] + self._inherits.values()
1938
1939         res = []
1940         if len(fields_pre):
1941             def convert_field(f):
1942                 if f in ('create_date', 'write_date'):
1943                     return "date_trunc('second', %s) as %s" % (f, f)
1944                 if f == self.CONCURRENCY_CHECK_FIELD:
1945                     if self._log_access:
1946                         return "COALESCE(write_date, create_date, now())::timestamp AS %s" % (f,)
1947                     return "now()::timestamp AS %s" % (f,)
1948                 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1949                     return 'length("%s") as "%s"' % (f, f)
1950                 return '"%s"' % (f,)
1951             fields_pre2 = map(convert_field, fields_pre)
1952             for i in range(0, len(ids), cr.IN_MAX):
1953                 sub_ids = ids[i:i+cr.IN_MAX]
1954                 if d1:
1955                     cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \
1956                             (','.join(fields_pre2 + ['id']), self._table,
1957                                 ','.join([str(x) for x in sub_ids]), d1,
1958                                 self._order), d2)
1959                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
1960                         raise except_orm(_('AccessError'),
1961                                 _('You try to bypass an access rule (Document type: %s).') % self._description)
1962                 else:
1963                     cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \
1964                             (','.join(fields_pre2 + ['id']), self._table,
1965                                 ','.join([str(x) for x in sub_ids]),
1966                                 self._order))
1967                 res.extend(cr.dictfetchall())
1968         else:
1969             res = map(lambda x: {'id': x}, ids)
1970
1971         for f in fields_pre:
1972             if f == self.CONCURRENCY_CHECK_FIELD:
1973                 continue
1974             if self._columns[f].translate:
1975                 ids = map(lambda x: x['id'], res)
1976                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
1977                 for r in res:
1978                     r[f] = res_trans.get(r['id'], False) or r[f]
1979
1980         for table in self._inherits:
1981             col = self._inherits[table]
1982             cols = intersect(self._inherit_fields.keys(), fields_to_read)
1983             if not cols:
1984                 continue
1985             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
1986
1987             res3 = {}
1988             for r in res2:
1989                 res3[r['id']] = r
1990                 del r['id']
1991
1992             for record in res:
1993                 record.update(res3[record[col]])
1994                 if col not in fields_to_read:
1995                     del record[col]
1996
1997         # all fields which need to be post-processed by a simple function (symbol_get)
1998         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
1999         if fields_post:
2000             # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need
2001             # to get the _symbol_get in each occurence
2002             for r in res:
2003                 for f in fields_post:
2004                     r[f] = self._columns[f]._symbol_get(r[f])
2005         ids = map(lambda x: x['id'], res)
2006
2007         # all non inherited fields for which the attribute whose name is in load is False
2008         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2009
2010         # Compute POST fields
2011         todo = {}
2012         for f in fields_post:
2013             todo.setdefault(self._columns[f]._multi, [])
2014             todo[self._columns[f]._multi].append(f)
2015         for key,val in todo.items():
2016             if key:
2017                 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2018                 for pos in val:
2019                     for record in res:
2020                         record[pos] = res2[record['id']][pos]
2021             else:
2022                 for f in val:
2023                     res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2024                     for record in res:
2025                         record[f] = res2[record['id']]
2026
2027 #for f in fields_post:
2028 #    # get the value of that field for all records/ids
2029 #    res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2030 #    for record in res:
2031 #        record[f] = res2[record['id']]
2032
2033         readonly = None
2034         for vals in res:
2035             for field in vals.copy():
2036                 fobj = None
2037                 if field in self._columns:
2038                     fobj = self._columns[field]
2039
2040                 if not fobj:
2041                     continue
2042                 groups = fobj.read
2043                 if groups:
2044                     edit = False
2045                     for group in groups:
2046                         module = group.split(".")[0]
2047                         grp = group.split(".")[1]
2048                         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" % \
2049                                    (grp, module, 'res.groups', user))
2050                         readonly = cr.fetchall()
2051                         if readonly[0][0] >= 1:
2052                             edit = True
2053                             break
2054                         elif readonly[0][0] == 0:
2055                             edit = False
2056                         else:
2057                             edit = False
2058
2059                     if not edit:
2060                         if type(vals[field]) == type([]):
2061                             vals[field] = []
2062                         elif type(vals[field]) == type(0.0):
2063                             vals[field] = 0
2064                         elif type(vals[field]) == type(''):
2065                             vals[field] = '=No Permission='
2066                         else:
2067                             vals[field] = False
2068         return res
2069
2070     def perm_read(self, cr, user, ids, context=None, details=True):
2071         if not context:
2072             context = {}
2073         if not ids:
2074             return []
2075         fields = ''
2076         if self._log_access:
2077             fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date'
2078         if isinstance(ids, (int, long)):
2079             ids_str = str(ids)
2080         else:
2081             ids_str = string.join(map(lambda x: str(x), ids), ',')
2082         cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
2083         res = cr.dictfetchall()
2084         for r in res:
2085             for key in r:
2086                 r[key] = r[key] or False
2087                 if key in ('write_uid', 'create_uid', 'uid') and details:
2088                     if r[key]:
2089                         r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
2090         if isinstance(ids, (int, long)):
2091             return res[ids]
2092         return res
2093
2094     def _check_concurrency(self, cr, ids, context):
2095         if not context:
2096             return
2097         if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
2098             def key(oid):
2099                 return "%s,%s" % (self._name, oid)
2100             santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
2101             for i in range(0, len(ids), cr.IN_MAX):
2102                 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
2103                                           for oid in ids[i:i+cr.IN_MAX]
2104                                           if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
2105                 if sub_ids:
2106                     cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
2107                     res = cr.fetchone()
2108                     if res and res[0]:
2109                         raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
2110
2111     def unlink(self, cr, uid, ids, context=None):
2112         if not ids:
2113             return True
2114         if isinstance(ids, (int, long)):
2115             ids = [ids]
2116
2117         result_store = self._store_get_values(cr, uid, ids, None, context)
2118
2119         self._check_concurrency(cr, ids, context)
2120
2121         self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink')
2122
2123         wf_service = netsvc.LocalService("workflow")
2124         for id in ids:
2125             wf_service.trg_delete(uid, self._name, id, cr)
2126
2127         #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids)
2128         #res = cr.dictfetchall()
2129         #for key in self._inherits:
2130         #   ids2 = [x[self._inherits[key]] for x in res]
2131         #   self.pool.get(key).unlink(cr, uid, ids2)
2132
2133         d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name)
2134         if d1:
2135             d1 = ' AND '+d1
2136
2137         for i in range(0, len(ids), cr.IN_MAX):
2138             sub_ids = ids[i:i+cr.IN_MAX]
2139             str_d = string.join(('%s',)*len(sub_ids), ',')
2140             if d1:
2141                 cr.execute('SELECT id FROM "'+self._table+'" ' \
2142                         'WHERE id IN ('+str_d+')'+d1, sub_ids+d2)
2143                 if not cr.rowcount == len({}.fromkeys(ids)):
2144                     raise except_orm(_('AccessError'),
2145                             _('You try to bypass an access rule (Document type: %s).') % \
2146                                     self._description)
2147
2148             if d1:
2149                 cr.execute('delete from "'+self._table+'" ' \
2150                         'where id in ('+str_d+')'+d1, sub_ids+d2)
2151             else:
2152                 cr.execute('delete from "'+self._table+'" ' \
2153                         'where id in ('+str_d+')', sub_ids)
2154
2155         for order, object, ids, fields in result_store:
2156             if object<>self._name:
2157                 obj =  self.pool.get(object)
2158                 cr.execute('select id from '+obj._table+' where id in ('+','.join(map(str, ids))+')')
2159                 ids = map(lambda x: x[0], cr.fetchall())
2160                 if ids:
2161                     obj._store_set_values(cr, uid, ids, fields, context)
2162         return True
2163
2164     #
2165     # TODO: Validate
2166     #
2167     def write(self, cr, user, ids, vals, context=None):
2168         readonly = None
2169         for field in vals.copy():
2170             fobj = None
2171             if field in self._columns:
2172                 fobj = self._columns[field]
2173             else:
2174                 fobj = self._inherit_fields[field][2]
2175             if not fobj:
2176                 continue
2177             groups = fobj.write
2178
2179             if groups:
2180                 edit = False
2181                 for group in groups:
2182                     module = group.split(".")[0]
2183                     grp = group.split(".")[1]
2184                     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" % \
2185                                (grp, module, 'res.groups', user))
2186                     readonly = cr.fetchall()
2187                     if readonly[0][0] >= 1:
2188                         edit = True
2189                         break
2190                     elif readonly[0][0] == 0:
2191                         edit = False
2192                     else:
2193                         edit = False
2194
2195                 if not edit:
2196                     vals.pop(field)
2197
2198         if not context:
2199             context = {}
2200         if not ids:
2201             return True
2202         if isinstance(ids, (int, long)):
2203             ids = [ids]
2204
2205         self._check_concurrency(cr, ids, context)
2206
2207         self.pool.get('ir.model.access').check(cr, user, self._name, 'write')
2208
2209         upd0 = []
2210         upd1 = []
2211         upd_todo = []
2212         updend = []
2213         direct = []
2214         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
2215         for field in vals:
2216             if field in self._columns:
2217                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
2218                     if (not totranslate) or not self._columns[field].translate:
2219                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
2220                         upd1.append(self._columns[field]._symbol_set[1](vals[field]))
2221                     direct.append(field)
2222                 else:
2223                     upd_todo.append(field)
2224             else:
2225                 updend.append(field)
2226             if field in self._columns \
2227                     and hasattr(self._columns[field], 'selection') \
2228                     and vals[field]:
2229                 if self._columns[field]._type == 'reference':
2230                     val = vals[field].split(',')[0]
2231                 else:
2232                     val = vals[field]
2233                 if isinstance(self._columns[field].selection, (tuple, list)):
2234                     if val not in dict(self._columns[field].selection):
2235                         raise except_orm(_('ValidateError'),
2236                         _('The value "%s" for the field "%s" is not in the selection') \
2237                                 % (vals[field], field))
2238                 else:
2239                     if val not in dict(self._columns[field].selection(
2240                         self, cr, user, context=context)):
2241                         raise except_orm(_('ValidateError'),
2242                         _('The value "%s" for the field "%s" is not in the selection') \
2243                                 % (vals[field], field))
2244
2245         if self._log_access:
2246             upd0.append('write_uid=%s')
2247             upd0.append('write_date=now()')
2248             upd1.append(user)
2249
2250         if len(upd0):
2251
2252             d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2253             if d1:
2254                 d1 = ' and '+d1
2255
2256             for i in range(0, len(ids), cr.IN_MAX):
2257                 sub_ids = ids[i:i+cr.IN_MAX]
2258                 ids_str = string.join(map(str, sub_ids), ',')
2259                 if d1:
2260                     cr.execute('SELECT id FROM "'+self._table+'" ' \
2261                             'WHERE id IN ('+ids_str+')'+d1, d2)
2262                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
2263                         raise except_orm(_('AccessError'),
2264                                 _('You try to bypass an access rule (Document type: %s).') % \
2265                                         self._description)
2266                 else:
2267                     cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')')
2268                     if not cr.rowcount == len({}.fromkeys(sub_ids)):
2269                         raise except_orm(_('AccessError'),
2270                                 _('You try to write on an record that doesn\'t exist ' \
2271                                         '(Document type: %s).') % self._description)
2272                 if d1:
2273                     cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \
2274                             'where id in ('+ids_str+')'+d1, upd1+ d2)
2275                 else:
2276                     cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \
2277                             'where id in ('+ids_str+')', upd1)
2278
2279             if totranslate:
2280                 for f in direct:
2281                     if self._columns[f].translate:
2282                         self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f])
2283
2284         # call the 'set' method of fields which are not classic_write
2285         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
2286
2287         # default element in context must be remove when call a one2many or many2many
2288         rel_context = context
2289         for c in context.items():
2290             if c[0].startswith('default_'):
2291                 del rel_context[c[0]]
2292
2293         for field in upd_todo:
2294             for id in ids:
2295                 self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context)
2296
2297         for table in self._inherits:
2298             col = self._inherits[table]
2299             nids = []
2300             for i in range(0, len(ids), cr.IN_MAX):
2301                 sub_ids = ids[i:i+cr.IN_MAX]
2302                 ids_str = string.join(map(str, sub_ids), ',')
2303                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2304                         'where id in ('+ids_str+')', upd1)
2305                 nids.extend([x[0] for x in cr.fetchall()])
2306
2307             v = {}
2308             for val in updend:
2309                 if self._inherit_fields[val][0] == table:
2310                     v[val] = vals[val]
2311             self.pool.get(table).write(cr, user, nids, v, context)
2312
2313         self._validate(cr, user, ids, context)
2314 # TODO: use _order to set dest at the right position and not first node of parent
2315         if self._parent_store and (self._parent_name in vals):
2316             if self.pool._init:
2317                 self.pool._init_parent[self._name]=True
2318             else:
2319                 for id in ids:
2320                     # Find Position of the element
2321                     if vals[self._parent_name]:
2322                         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],))
2323                     else:
2324                         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))
2325                     result_p = cr.fetchall()
2326                     position = None
2327                     for (pleft,pright,pid) in result_p:
2328                         if pid == id:
2329                             break
2330                         position = pright+1
2331
2332                     # It's the first node of the parent: position = parent_left+1
2333                     if not position:
2334                         if not vals[self._parent_name]:
2335                             position = 1
2336                         else:
2337                             cr.execute('select parent_left from '+self._table+' where id=%s', (vals[self._parent_name],))
2338                             position = cr.fetchone()[0]+1
2339
2340                     # We have the new position !
2341                     cr.execute('select parent_left,parent_right from '+self._table+' where id=%s', (id,))
2342                     pleft,pright = cr.fetchone()
2343                     distance = pright - pleft + 1
2344
2345                     if position>pleft and position<=pright:
2346                         raise except_orm(_('UserError'), _('Recursivity Detected.'))
2347
2348                     if pleft<position:
2349                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2350                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2351                         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))
2352                     else:
2353                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
2354                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2355                         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))
2356
2357         result = self._store_get_values(cr, user, ids, vals.keys(), context)
2358         for order, object, ids, fields in result:
2359             self.pool.get(object)._store_set_values(cr, user, ids, fields, context)
2360
2361         wf_service = netsvc.LocalService("workflow")
2362         for id in ids:
2363             wf_service.trg_write(user, self._name, id, cr)
2364         return True
2365
2366     #
2367     # TODO: Should set perm to user.xxx
2368     #
2369     def create(self, cr, user, vals, context=None):
2370         """ create(cr, user, vals, context) -> int
2371         cr = database cursor
2372         user = user id
2373         vals = dictionary of the form {'field_name':field_value, ...}
2374         """
2375         if not context:
2376             context = {}
2377         self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
2378
2379         default = []
2380
2381         avoid_table = []
2382         for (t, c) in self._inherits.items():
2383             if c in vals:
2384                 avoid_table.append(t)
2385         for f in self._columns.keys(): # + self._inherit_fields.keys():
2386             if not f in vals:
2387                 default.append(f)
2388
2389         for f in self._inherit_fields.keys():
2390             if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
2391                 default.append(f)
2392
2393         if len(default):
2394             default_values = self.default_get(cr, user, default, context)
2395             for dv in default_values:
2396                 if dv in self._columns and self._columns[dv]._type == 'many2many':
2397                     if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
2398                         default_values[dv] = [(6, 0, default_values[dv])]
2399
2400             vals.update(default_values)
2401
2402         tocreate = {}
2403         for v in self._inherits:
2404             if self._inherits[v] not in vals:
2405                 tocreate[v] = {}
2406
2407         (upd0, upd1, upd2) = ('', '', [])
2408         upd_todo = []
2409
2410         for v in vals.keys():
2411             if v in self._inherit_fields:
2412                 (table, col, col_detail) = self._inherit_fields[v]
2413                 tocreate[table][v] = vals[v]
2414                 del vals[v]
2415
2416         # Try-except added to filter the creation of those records whose filds are readonly.
2417         # Example : any dashboard which has all the fields readonly.(due to Views(database views))
2418         try:
2419             cr.execute("SELECT nextval('"+self._sequence+"')")
2420         except:
2421             raise except_orm(_('UserError'),
2422                         _('You cannot perform this operation.'))
2423
2424         id_new = cr.fetchone()[0]
2425         for table in tocreate:
2426             id = self.pool.get(table).create(cr, user, tocreate[table])
2427             upd0 += ','+self._inherits[table]
2428             upd1 += ',%s'
2429             upd2.append(id)
2430         
2431         #Start : Set bool fields to be False if they are not touched(to make search more powerful) 
2432         bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
2433         
2434         for bool_field in bool_fields:
2435             if bool_field not in vals:
2436                 vals[bool_field] = False
2437         #End
2438         
2439         for field in vals:
2440             if self._columns[field]._classic_write:
2441                 upd0 = upd0 + ',"' + field + '"'
2442                 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
2443                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
2444             else:
2445                 upd_todo.append(field)
2446             if field in self._columns \
2447                     and hasattr(self._columns[field], 'selection') \
2448                     and vals[field]:
2449                 if self._columns[field]._type == 'reference':
2450                     val = vals[field].split(',')[0]
2451                 else:
2452                     val = vals[field]
2453                 if isinstance(self._columns[field].selection, (tuple, list)):
2454                     if val not in dict(self._columns[field].selection):
2455                         raise except_orm(_('ValidateError'),
2456                         _('The value "%s" for the field "%s" is not in the selection') \
2457                                 % (vals[field], field))
2458                 else:
2459                     if val not in dict(self._columns[field].selection(
2460                         self, cr, user, context=context)):
2461                         raise except_orm(_('ValidateError'),
2462                         _('The value "%s" for the field "%s" is not in the selection') \
2463                                 % (vals[field], field))
2464         if self._log_access:
2465             upd0 += ',create_uid,create_date'
2466             upd1 += ',%s,now()'
2467             upd2.append(user)
2468         cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
2469         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
2470
2471         if self._parent_store:
2472             if self.pool._init:
2473                 self.pool._init_parent[self._name]=True
2474             else:
2475                 parent = vals.get(self._parent_name, False)
2476                 if parent:
2477                     cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
2478                     pleft_old = None
2479                     result_p = cr.fetchall()
2480                     for (pleft,) in result_p:
2481                         if not pleft:
2482                             break
2483                         pleft_old = pleft
2484                     if not pleft_old:
2485                         cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
2486                         pleft_old = cr.fetchone()[0]
2487                     pleft = pleft_old
2488                 else:
2489                     cr.execute('select max(parent_right) from '+self._table)
2490                     pleft = cr.fetchone()[0] or 0
2491                 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
2492                 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
2493                 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
2494                 
2495         # default element in context must be remove when call a one2many or many2many
2496         rel_context = context
2497         for c in context.items():
2498             if c[0].startswith('default_'):
2499                 del rel_context[c[0]]
2500
2501         for field in upd_todo:
2502             self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context)
2503         self._validate(cr, user, [id_new], context)
2504
2505         result = self._store_get_values(cr, user, [id_new], vals.keys(), context)
2506         for order, object, ids, fields in result:
2507             self.pool.get(object)._store_set_values(cr, user, ids, fields, context)
2508
2509         wf_service = netsvc.LocalService("workflow")
2510         wf_service.trg_create(user, self._name, id_new, cr)
2511         return id_new
2512
2513     def _store_get_values(self, cr, uid, ids, fields, context):
2514         result = {}
2515         fncts = self.pool._store_function.get(self._name, [])
2516         for fnct in range(len(fncts)):
2517             result.setdefault(fncts[fnct][0], {})
2518             ids2 = fncts[fnct][2](self,cr, uid, ids, context)
2519             for id in filter(None, ids2):
2520                 result[fncts[fnct][0]].setdefault(id, [])
2521                 result[fncts[fnct][0]][id].append(fnct)
2522         dict = {}
2523         for object in result:
2524             k2 = {}
2525             for id,fnct in result[object].items():
2526                 k2.setdefault(tuple(fnct), [])
2527                 k2[tuple(fnct)].append(id)
2528             for fnct,id in k2.items():
2529                 dict.setdefault(fncts[fnct[0]][4],[])
2530                 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
2531         result2 = []
2532         tmp = dict.keys()
2533         tmp.sort()
2534         for k in tmp:
2535             result2+=dict[k]
2536         return result2
2537
2538     def _store_set_values(self, cr, uid, ids, fields, context):
2539         todo = {}
2540         keys = []
2541         for f in fields:
2542             if self._columns[f]._multi not in keys:
2543                 keys.append(self._columns[f]._multi)
2544             todo.setdefault(self._columns[f]._multi, [])
2545             todo[self._columns[f]._multi].append(f)
2546         for key in keys:
2547             val = todo[key]
2548             if key:
2549                 result = self._columns[val[0]].get(cr, self, ids, val, uid, context=context)
2550                 for id,value in result.items():
2551                     upd0 = []
2552                     upd1 = []
2553                     for v in value:
2554                         if v not in val:
2555                             continue
2556                         if self._columns[v]._type in ('many2one', 'one2one'):
2557                             try:
2558                                 value[v] = value[v][0]
2559                             except:
2560                                 pass
2561                         upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
2562                         upd1.append(self._columns[v]._symbol_set[1](value[v]))
2563                     upd1.append(id)
2564                     cr.execute('update "' + self._table + '" set ' + \
2565                         string.join(upd0, ',') + ' where id = %s', upd1)
2566
2567             else:
2568                 for f in val:
2569                     result = self._columns[f].get(cr, self, ids, f, uid, context=context)
2570                     for id,value in result.items():
2571                         if self._columns[f]._type in ('many2one', 'one2one'):
2572                             try:
2573                                 value = value[0]
2574                             except:
2575                                 pass
2576                         cr.execute('update "' + self._table + '" set ' + \
2577                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
2578         return True
2579
2580     #
2581     # TODO: Validate
2582     #
2583     def perm_write(self, cr, user, ids, fields, context=None):
2584         raise _('This method does not exist anymore')
2585
2586     # TODO: ameliorer avec NULL
2587     def _where_calc(self, cr, user, args, active_test=True, context=None):
2588         if not context:
2589             context = {}
2590         args = args[:]
2591         # if the object has a field named 'active', filter out all inactive
2592         # records unless they were explicitely asked for
2593         if 'active' in self._columns and (active_test and context.get('active_test', True)):
2594             if args:
2595                 active_in_args = False
2596                 for a in args:
2597                     if a[0] == 'active':
2598                         active_in_args = True
2599                 if not active_in_args:
2600                     args.insert(0, ('active', '=', 1))
2601             else:
2602                 args = [('active', '=', 1)]
2603
2604         if args:
2605             import expression
2606             e = expression.expression(args)
2607             e.parse(cr, user, self, context)
2608             tables = e.get_tables()
2609             qu1, qu2 = e.to_sql()
2610             qu1 = qu1 and [qu1] or []
2611         else:
2612             qu1, qu2, tables = [], [], ['"%s"' % self._table]
2613
2614         return (qu1, qu2, tables)
2615
2616     def _check_qorder(self, word):
2617         if not regex_order.match(word):
2618             raise except_orm(_('AccessError'), _('Bad query.'))
2619         return True
2620
2621     def search(self, cr, user, args, offset=0, limit=None, order=None,
2622             context=None, count=False):
2623         if not context:
2624             context = {}
2625         # compute the where, order by, limit and offset clauses
2626         (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
2627
2628         if len(qu1):
2629             qu1 = ' where '+string.join(qu1, ' and ')
2630         else:
2631             qu1 = ''
2632
2633         if order:
2634             self._check_qorder(order)
2635         order_by = order or self._order
2636
2637         limit_str = limit and ' limit %d' % limit or ''
2638         offset_str = offset and ' offset %d' % offset or ''
2639
2640
2641         # construct a clause for the rules :
2642         d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
2643         if d1:
2644             qu1 = qu1 and qu1+' and '+d1 or ' where '+d1
2645             qu2 += d2
2646
2647         if count:
2648             cr.execute('select count(%s.id) from ' % self._table +
2649                     ','.join(tables) +qu1 + limit_str + offset_str, qu2)
2650             res = cr.fetchall()
2651             return res[0][0]
2652         # execute the "main" query to fetch the ids we were searching for
2653         cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
2654         res = cr.fetchall()
2655         return [x[0] for x in res]
2656
2657     # returns the different values ever entered for one field
2658     # this is used, for example, in the client when the user hits enter on
2659     # a char field
2660     def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
2661         if not args:
2662             args = []
2663         if field in self._inherit_fields:
2664             return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
2665         else:
2666             return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
2667
2668     def name_get(self, cr, user, ids, context=None):
2669         if not context:
2670             context = {}
2671         if not ids:
2672             return []
2673         if isinstance(ids, (int, long)):
2674             ids = [ids]
2675         return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
2676             [self._rec_name], context, load='_classic_write')]
2677
2678     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=None):
2679         if not args:
2680             args = []
2681         if not context:
2682             context = {}
2683         args = args[:]
2684         if name:
2685             args += [(self._rec_name, operator, name)]
2686         ids = self.search(cr, user, args, limit=limit, context=context)
2687         res = self.name_get(cr, user, ids, context)
2688         return res
2689
2690     def copy_data(self, cr, uid, id, default=None, context=None):
2691         if not context:
2692             context = {}
2693         if not default:
2694             default = {}
2695         if 'state' not in default:
2696             if 'state' in self._defaults:
2697                 default['state'] = self._defaults['state'](self, cr, uid, context)
2698         data = self.read(cr, uid, [id], context=context)[0]
2699         fields = self.fields_get(cr, uid)
2700         trans_data=[]
2701         for f in fields:
2702             ftype = fields[f]['type']
2703
2704             if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
2705                 del data[f]
2706
2707             if f in default:
2708                 data[f] = default[f]
2709             elif ftype == 'function':
2710                 del data[f]
2711             elif ftype == 'many2one':
2712                 try:
2713                     data[f] = data[f] and data[f][0]
2714                 except:
2715                     pass
2716             elif ftype in ('one2many', 'one2one'):
2717                 res = []
2718                 rel = self.pool.get(fields[f]['relation'])
2719                 for rel_id in data[f]:
2720                     # the lines are first duplicated using the wrong (old)
2721                     # parent but then are reassigned to the correct one thanks
2722                     # to the (4, ...)
2723                     d,t = rel.copy_data(cr, uid, rel_id, context=context)
2724                     res.append((0, 0, d))
2725                     trans_data += t
2726                 data[f] = res
2727             elif ftype == 'many2many':
2728                 data[f] = [(6, 0, data[f])]
2729
2730         trans_obj = self.pool.get('ir.translation')
2731         trans_name=''
2732         for f in fields:
2733             trans_flag=True
2734             if f in self._columns and self._columns[f].translate:
2735                 trans_name=self._name+","+f
2736             elif f in self._inherit_fields and self._inherit_fields[f][2].translate:
2737                 trans_name=self._inherit_fields[f][0]+","+f
2738             else:
2739                 trans_flag=False
2740
2741             if trans_flag:
2742                 trans_ids = trans_obj.search(cr, uid, [
2743                         ('name', '=', trans_name),
2744                         ('res_id','=',data['id'])
2745                     ])
2746
2747                 trans_data.extend(trans_obj.read(cr,uid,trans_ids,context=context))
2748
2749         del data['id']
2750
2751         for v in self._inherits:
2752             del data[self._inherits[v]]
2753         return data, trans_data
2754
2755     def copy(self, cr, uid, id, default=None, context=None):
2756         trans_obj = self.pool.get('ir.translation')
2757         data, trans_data = self.copy_data(cr, uid, id, default, context)
2758         new_id=self.create(cr, uid, data)
2759         for record in trans_data:
2760             del record['id']
2761             record['res_id']=new_id
2762             trans_obj.create(cr,uid,record)
2763         return new_id
2764
2765     def exists(self, cr, uid, id, context=None):
2766         cr.execute('SELECT count(1) FROM "%s" where id=%%s' % (self._table,), (id,))
2767         return bool(cr.fetchone()[0])
2768
2769     def check_recursion(self, cr, uid, ids, parent=None):
2770         if not parent:
2771             parent = self._parent_name
2772         ids_parent = ids[:]
2773         while len(ids_parent):
2774             ids_parent2 = []
2775             for i in range(0, len(ids), cr.IN_MAX):
2776                 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
2777                 cr.execute('SELECT distinct "'+parent+'"'+
2778                     ' FROM "'+self._table+'" ' \
2779                     'WHERE id in ('+','.join(map(str, sub_ids_parent))+')')
2780                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
2781             ids_parent = ids_parent2
2782             for i in ids_parent:
2783                 if i in ids:
2784                     return False
2785         return True
2786
2787
2788 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2789