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