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