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