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