1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
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
32 # . Multi-level caching system
33 # . 2 different inheritancies
35 # - classicals (varchar, integer, boolean, ...)
36 # - relations (one2many, many2one, many2many)
52 from lxml import etree
53 from tools.config import config
54 from tools.translate import _
57 from query import Query
59 from tools.safe_eval import safe_eval as eval
61 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
64 POSTGRES_CONFDELTYPES = {
72 def last_day_of_current_month():
73 today = datetime.date.today()
74 last_day = str(calendar.monthrange(today.year, today.month)[1])
75 return time.strftime('%Y-%m-' + last_day)
77 def intersect(la, lb):
78 return filter(lambda x: x in lb, la)
80 class except_orm(Exception):
81 def __init__(self, name, value):
84 self.args = (name, value)
86 class BrowseRecordError(Exception):
89 # Readonly python database object browser
90 class browse_null(object):
95 def __getitem__(self, name):
98 def __getattr__(self, name):
99 return None # XXX: return self ?
107 def __nonzero__(self):
110 def __unicode__(self):
115 # TODO: execute an object method on browse_record_list
117 class browse_record_list(list):
119 def __init__(self, lst, context=None):
122 super(browse_record_list, self).__init__(lst)
123 self.context = context
126 class browse_record(object):
127 logger = netsvc.Logger()
129 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
131 table : the object (inherited from orm)
132 context : dictionary with an optional context
134 if fields_process is None:
138 self._list_class = list_class or browse_record_list
143 self._table_name = self._table._name
144 self.__logger = logging.getLogger(
145 'osv.browse_record.' + self._table_name)
146 self._context = context
147 self._fields_process = fields_process
149 cache.setdefault(table._name, {})
150 self._data = cache[table._name]
152 if not (id and isinstance(id, (int, long,))):
153 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
154 # if not table.exists(cr, uid, id, context):
155 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
157 if id not in self._data:
158 self._data[id] = {'id': id}
162 def __getitem__(self, name):
166 if name not in self._data[self._id]:
167 # build the list of fields we will fetch
169 # fetch the definition of the field which was asked for
170 if name in self._table._columns:
171 col = self._table._columns[name]
172 elif name in self._table._inherit_fields:
173 col = self._table._inherit_fields[name][2]
174 elif hasattr(self._table, str(name)):
175 attr = getattr(self._table, name)
177 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
178 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
182 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
183 "Field '%s' does not exist in object '%s': \n%s" % (
184 name, self, ''.join(traceback.format_exc())))
185 raise KeyError("Field '%s' does not exist in object '%s'" % (
188 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
190 # gen the list of "local" (ie not inherited) fields which are classic or many2one
191 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
192 # gen the list of inherited fields
193 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
194 # complete the field list with the inherited fields which are classic or many2one
195 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
196 # otherwise we fetch only that field
198 fields_to_fetch = [(name, col)]
199 ids = filter(lambda id: name not in self._data[id], self._data.keys())
201 field_names = map(lambda x: x[0], fields_to_fetch)
202 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
203 if self._fields_process:
204 lang = self._context.get('lang', 'en_US') or 'en_US'
205 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
207 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
208 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
210 for field_name, field_column in fields_to_fetch:
211 if field_column._type in self._fields_process:
212 for result_line in field_values:
213 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
214 if result_line[field_name]:
215 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
218 # Where did those ids come from? Perhaps old entries in ir_model_dat?
219 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
220 raise KeyError('Field %s not found in %s'%(name, self))
221 # create browse records for 'remote' objects
222 for result_line in field_values:
224 for field_name, field_column in fields_to_fetch:
225 if field_column._type in ('many2one', 'one2one'):
226 if result_line[field_name]:
227 obj = self._table.pool.get(field_column._obj)
228 if isinstance(result_line[field_name], (list, tuple)):
229 value = result_line[field_name][0]
231 value = result_line[field_name]
233 # FIXME: this happen when a _inherits object
234 # overwrite a field of it parent. Need
235 # testing to be sure we got the right
236 # object and not the parent one.
237 if not isinstance(value, browse_record):
238 new_data[field_name] = browse_record(self._cr,
239 self._uid, value, obj, self._cache,
240 context=self._context,
241 list_class=self._list_class,
242 fields_process=self._fields_process)
244 new_data[field_name] = value
246 new_data[field_name] = browse_null()
248 new_data[field_name] = browse_null()
249 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
250 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)
251 elif field_column._type in ('reference'):
252 if result_line[field_name]:
253 if isinstance(result_line[field_name], browse_record):
254 new_data[field_name] = result_line[field_name]
256 ref_obj, ref_id = result_line[field_name].split(',')
257 ref_id = long(ref_id)
258 obj = self._table.pool.get(ref_obj)
259 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)
261 new_data[field_name] = browse_null()
263 new_data[field_name] = result_line[field_name]
264 self._data[result_line['id']].update(new_data)
266 if not name in self._data[self._id]:
267 #how did this happen?
268 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
269 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
270 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
271 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
272 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
273 return self._data[self._id][name]
275 def __getattr__(self, name):
279 raise AttributeError(e)
281 def __contains__(self, name):
282 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
284 def __hasattr__(self, name):
291 return "browse_record(%s, %d)" % (self._table_name, self._id)
293 def __eq__(self, other):
294 if not isinstance(other, browse_record):
296 return (self._table_name, self._id) == (other._table_name, other._id)
298 def __ne__(self, other):
299 if not isinstance(other, browse_record):
301 return (self._table_name, self._id) != (other._table_name, other._id)
303 # we need to define __unicode__ even though we've already defined __str__
304 # because we have overridden __getattr__
305 def __unicode__(self):
306 return unicode(str(self))
309 return hash((self._table_name, self._id))
317 (type returned by postgres when the column was created, type expression to create the column)
321 fields.boolean: 'bool',
322 fields.integer: 'int4',
323 fields.integer_big: 'int8',
327 fields.datetime: 'timestamp',
328 fields.binary: 'bytea',
329 fields.many2one: 'int4',
331 if type(f) in type_dict:
332 f_type = (type_dict[type(f)], type_dict[type(f)])
333 elif isinstance(f, fields.float):
335 f_type = ('numeric', 'NUMERIC')
337 f_type = ('float8', 'DOUBLE PRECISION')
338 elif isinstance(f, (fields.char, fields.reference)):
339 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
340 elif isinstance(f, fields.selection):
341 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
342 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
343 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
346 f_size = getattr(f, 'size', None) or 16
349 f_type = ('int4', 'INTEGER')
351 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
352 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
353 t = eval('fields.'+(f._type), globals())
354 f_type = (type_dict[t], type_dict[t])
355 elif isinstance(f, fields.function) and f._type == 'float':
357 f_type = ('numeric', 'NUMERIC')
359 f_type = ('float8', 'DOUBLE PRECISION')
360 elif isinstance(f, fields.function) and f._type == 'selection':
361 f_type = ('text', 'text')
362 elif isinstance(f, fields.function) and f._type == 'char':
363 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
365 logger = netsvc.Logger()
366 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
371 class orm_template(object):
377 _parent_name = 'parent_id'
378 _parent_store = False
379 _parent_order = False
389 CONCURRENCY_CHECK_FIELD = '__last_update'
390 def log(self, cr, uid, id, message, secondary=False, context=None):
391 return self.pool.get('res.log').create(cr, uid,
394 'res_model': self._name,
395 'secondary': secondary,
401 def view_init(self, cr, uid, fields_list, context=None):
402 """Override this method to do specific things when a view on the object is opened."""
405 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
406 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
408 def _field_create(self, cr, context=None):
411 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
413 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
414 model_id = cr.fetchone()[0]
415 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'))
417 model_id = cr.fetchone()[0]
418 if 'module' in context:
419 name_id = 'model_'+self._name.replace('.', '_')
420 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id, model_id, context['module']))
422 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
423 (name_id, context['module'], 'ir.model', model_id)
428 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
430 for rec in cr.dictfetchall():
431 cols[rec['name']] = rec
433 for (k, f) in self._columns.items():
435 'model_id': model_id,
438 'field_description': f.string.replace("'", " "),
440 'relation': f._obj or '',
441 'view_load': (f.view_load and 1) or 0,
442 'select_level': tools.ustr(f.select or 0),
443 'readonly': (f.readonly and 1) or 0,
444 'required': (f.required and 1) or 0,
445 'selectable': (f.selectable and 1) or 0,
446 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
448 # When its a custom field,it does not contain f.select
449 if context.get('field_state', 'base') == 'manual':
450 if context.get('field_name', '') == k:
451 vals['select_level'] = context.get('select', '0')
452 #setting value to let the problem NOT occur next time
454 vals['select_level'] = cols[k]['select_level']
457 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
458 id = cr.fetchone()[0]
460 cr.execute("""INSERT INTO ir_model_fields (
461 id, model_id, model, name, field_description, ttype,
462 relation,view_load,state,select_level,relation_field
464 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
466 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
467 vals['relation'], bool(vals['view_load']), 'base',
468 vals['select_level'], vals['relation_field']
470 if 'module' in context:
471 name1 = 'field_' + self._table + '_' + k
472 cr.execute("select name from ir_model_data where name=%s", (name1,))
474 name1 = name1 + "_" + str(id)
475 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
476 (name1, context['module'], 'ir.model.fields', id)
479 for key, val in vals.items():
480 if cols[k][key] != vals[key]:
481 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
483 cr.execute("""UPDATE ir_model_fields SET
484 model_id=%s, field_description=%s, ttype=%s, relation=%s,
485 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
487 model=%s AND name=%s""", (
488 vals['model_id'], vals['field_description'], vals['ttype'],
489 vals['relation'], bool(vals['view_load']),
490 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
495 def _auto_init(self, cr, context=None):
496 self._field_create(cr, context=context)
498 def __init__(self, cr):
499 if not self._name and not hasattr(self, '_inherit'):
500 name = type(self).__name__.split('.')[0]
501 msg = "The class %s has to have a _name attribute" % name
503 logger = netsvc.Logger()
504 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
505 raise except_orm('ValueError', msg)
507 if not self._description:
508 self._description = self._name
510 self._table = self._name.replace('.', '_')
512 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
514 Fetch records as objects allowing to use dot notation to browse fields and relations
516 :param cr: database cursor
517 :param user: current user id
518 :param select: id or list of ids
519 :param context: context arguments, like lang, time zone
520 :rtype: object or list of objects requested
523 self._list_class = list_class or browse_record_list
525 # need to accepts ints and longs because ids coming from a method
526 # launched by button in the interface have a type long...
527 if isinstance(select, (int, long)):
528 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
529 elif isinstance(select, list):
530 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)
534 def __export_row(self, cr, uid, row, fields, context=None):
538 def check_type(field_type):
539 if field_type == 'float':
541 elif field_type == 'integer':
543 elif field_type == 'boolean':
547 def selection_field(in_field):
548 col_obj = self.pool.get(in_field.keys()[0])
549 if f[i] in col_obj._columns.keys():
550 return col_obj._columns[f[i]]
551 elif f[i] in col_obj._inherits.keys():
552 selection_field(col_obj._inherits)
557 data = map(lambda x: '', range(len(fields)))
559 for fpos in range(len(fields)):
568 model_data = self.pool.get('ir.model.data')
569 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
571 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
573 r = '%s.%s' % (d['module'], d['name'])
580 # To display external name of selection field when its exported
581 if not context.get('import_comp', False):# Allow external name only if its not import compatible
583 if f[i] in self._columns.keys():
584 cols = self._columns[f[i]]
585 elif f[i] in self._inherit_fields.keys():
586 cols = selection_field(self._inherits)
587 if cols and cols._type == 'selection':
588 sel_list = cols.selection
589 if r and type(sel_list) == type([]):
590 r = [x[1] for x in sel_list if r==x[0]]
591 r = r and r[0] or False
593 if f[i] in self._columns:
594 r = check_type(self._columns[f[i]]._type)
595 elif f[i] in self._inherit_fields:
596 r = check_type(self._inherit_fields[f[i]][2]._type)
599 if isinstance(r, (browse_record_list, list)):
601 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
604 if [x for x in fields2 if x]:
608 lines2 = self.__export_row(cr, uid, row2, fields2,
611 for fpos2 in range(len(fields)):
612 if lines2 and lines2[0][fpos2]:
613 data[fpos2] = lines2[0][fpos2]
617 if isinstance(rr.name, browse_record):
619 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
620 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
621 dt += tools.ustr(rr_name or '') + ','
631 if isinstance(r, browse_record):
632 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
633 r = r and r[0] and r[0][1] or ''
634 data[fpos] = tools.ustr(r or '')
635 return [data] + lines
637 def export_data(self, cr, uid, ids, fields_to_export, context=None):
639 Export fields for selected objects
641 :param cr: database cursor
642 :param uid: current user id
643 :param ids: list of ids
644 :param fields_to_export: list of fields
645 :param context: context arguments, like lang, time zone, may contain import_comp(default: False) to make exported data compatible with import_data()
646 :rtype: dictionary with a *datas* matrix
648 This method is used when exporting data via client menu
653 imp_comp = context.get('import_comp', False)
654 cols = self._columns.copy()
655 for f in self._inherit_fields:
656 cols.update({f: self._inherit_fields[f][2]})
657 fields_to_export = map(lambda x: x.split('/'), fields_to_export)
658 fields_export = fields_to_export + []
661 for field in fields_export:
662 if imp_comp and len(field) > 1:
663 warning_fields.append('/'.join(map(lambda x: x in cols and cols[x].string or x,field)))
664 elif len (field) <= 1:
665 if imp_comp and cols.get(field and field[0], False):
666 if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
667 or isinstance(cols[field[0]], fields.related)\
668 or isinstance(cols[field[0]], fields.one2many)):
669 warning_fields.append('/'.join(map(lambda x: x in cols and cols[x].string or x,field)))
671 if imp_comp and len(warning_fields):
672 warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' % ('\n'.join(warning_fields))
674 return {'warning': warning}
675 for row in self.browse(cr, uid, ids, context):
676 datas += self.__export_row(cr, uid, row, fields_to_export, context)
677 return {'datas': datas}
679 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
681 Import given data in given module
683 :param cr: database cursor
684 :param uid: current user id
685 :param ids: list of ids
686 :param fields: list of fields
687 :param data: data to import
688 :param mode: 'init' or 'update' for record creation
689 :param current_module: module name
690 :param noupdate: flag for record creation
691 :param context: context arguments, like lang, time zone,
692 :param filename: optional file to store partial import state for recovery
695 This method is used when importing data via client menu
700 fields = map(lambda x: x.split('/'), fields)
701 logger = netsvc.Logger()
702 ir_model_data_obj = self.pool.get('ir.model.data')
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))])
708 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
711 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
712 line = datas[position]
721 ir_model_data_obj = self.pool.get('ir.model.data')
723 # Import normal fields
725 for i in range(len(fields)):
727 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
732 if prefix and not prefix[0] in field:
735 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
739 field_name = field[0].split(':')[0]
740 model_rel = fields_def[field_name]['relation']
742 if fields_def[field[len(prefix)][:-6]]['type'] == 'many2many':
744 for db_id in line[i].split(config.get('csv_internal_sep')):
746 _check_db_id(self, model_rel, db_id)
749 warning += [tools.exception_to_unicode(e)]
750 logger.notifyChannel("import", netsvc.LOG_ERROR,
751 tools.exception_to_unicode(e))
753 res = [(6, 0, res_id)]
756 _check_db_id(self, model_rel, line[i])
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
765 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
768 if fields_def[field[len(prefix)][:-3]]['type'] == 'many2many':
770 for word in line[i].split(config.get('csv_internal_sep')):
772 module, xml_id = word.rsplit('.', 1)
774 module, xml_id = current_module, word
775 id = ir_model_data_obj._get_id(cr, uid, module,
777 res_id2 = ir_model_data_obj.read(cr, uid, [id],
778 ['res_id'])[0]['res_id']
780 res_id.append(res_id2)
782 res_id = [(6, 0, res_id)]
785 module, xml_id = line[i].rsplit('.', 1)
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'])
791 res_id = ir_model_data[0]['res_id']
793 raise ValueError('No references to %s.%s' % (module, xml_id))
794 row[field[-1][:-3]] = res_id or False
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
801 if (len(field) == len(prefix)+1) and \
802 (prefix == field[0:len(prefix)]):
803 if field[len(prefix)] == "id":
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)])
812 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
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})
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))
825 if field[len(prefix)] == "db_id":
828 _check_db_id(self, model_name, line[i])
829 data_res_id = is_db_id = int(line[i])
831 warning += [tools.exception_to_unicode(e)]
832 logger.notifyChannel("import", netsvc.LOG_ERROR,
833 tools.exception_to_unicode(e))
835 data_ids = ir_model_data_obj.search(cr, uid, [('model', '=', model_name), ('res_id', '=', line[i])])
837 d = ir_model_data_obj.read(cr, uid, data_ids, ['name', 'module'])[0]
840 data_id = '%s.%s' % (d['module'], d['name'])
843 if is_xml_id and not data_id:
845 if is_xml_id and is_xml_id != data_id:
846 warning += [_("Id is not the same than existing one: %s") % (line[i])]
847 logger.notifyChannel("import", netsvc.LOG_ERROR,
848 _("Id is not the same than existing one: %s") % (line[i]))
851 if fields_def[field[len(prefix)]]['type'] == 'integer':
852 res = line[i] and int(line[i])
853 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
854 res = line[i].lower() not in ('0', 'false', 'off')
855 elif fields_def[field[len(prefix)]]['type'] == 'float':
856 res = line[i] and float(line[i])
857 elif fields_def[field[len(prefix)]]['type'] == 'selection':
859 if isinstance(fields_def[field[len(prefix)]]['selection'],
861 sel = fields_def[field[len(prefix)]]['selection']
863 sel = fields_def[field[len(prefix)]]['selection'](self,
866 if line[i] in [tools.ustr(key), tools.ustr(val)]: #Acepting key or value for selection field
869 if line[i] and not res:
870 logger.notifyChannel("import", netsvc.LOG_WARNING,
871 _("key '%s' not found in selection field '%s'") % \
872 (line[i], field[len(prefix)]))
874 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
876 elif fields_def[field[len(prefix)]]['type'] == 'many2one':
879 relation = fields_def[field[len(prefix)]]['relation']
880 res2 = self.pool.get(relation).name_search(cr, uid,
881 line[i], [], operator='=', context=context)
882 res = (res2 and res2[0][0]) or False
884 warning += [_("Relation not found: %s on '%s'") % (line[i], relation)]
885 logger.notifyChannel("import", netsvc.LOG_WARNING,
886 _("Relation not found: %s on '%s'") % (line[i], relation))
887 elif fields_def[field[len(prefix)]]['type'] == 'many2many':
890 relation = fields_def[field[len(prefix)]]['relation']
891 for word in line[i].split(config.get('csv_internal_sep')):
892 res2 = self.pool.get(relation).name_search(cr,
893 uid, word, [], operator='=', context=context)
894 res3 = (res2 and res2[0][0]) or False
896 warning += [_("Relation not found: %s on '%s'") % (line[i], relation)]
897 logger.notifyChannel("import",
899 _("Relation not found: %s on '%s'") % (line[i], relation))
905 res = line[i] or False
906 row[field[len(prefix)]] = res
907 elif (prefix==field[0:len(prefix)]):
908 if field[0] not in todo:
909 todo.append(field[len(prefix)])
911 # Import one2many, many2many fields
915 relation_obj = self.pool.get(fields_def[field]['relation'])
916 newfd = relation_obj.fields_get(
917 cr, uid, context=context)
918 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
919 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
920 nbrmax = max(nbrmax, max2)
921 warning = warning + w2
922 reduce(lambda x, y: x and y, newrow)
923 row[field] = newrow and (reduce(lambda x, y: x or y, newrow.values()) and \
924 [(0, 0, newrow)]) or []
926 while (position+i) < len(datas):
928 for j in range(len(fields)):
930 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
935 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
936 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
937 warning = warning + w2
938 if newrow and reduce(lambda x, y: x or y, newrow.values()):
939 row[field].append((0, 0, newrow))
941 nbrmax = max(nbrmax, i)
944 for i in range(max(nbrmax, 1)):
947 result = (row, nbrmax, warning, translate, data_id, data_res_id)
950 fields_def = self.fields_get(cr, uid, context=context)
953 initial_size = len(datas)
954 if config.get('import_partial', False) and filename:
955 data = pickle.load(file(config.get('import_partial')))
956 original_value = data.get(filename, 0)
962 (res, other, warning, translate, data_id, res_id) = \
963 process_liness(self, datas, [], current_module, self._name, fields_def)
966 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
969 id = ir_model_data_obj._update(cr, uid, self._name,
970 current_module, res, xml_id=data_id, mode=mode,
971 noupdate=noupdate, res_id=res_id, context=context)
976 if isinstance(e, psycopg2.IntegrityError):
977 msg = _('Insertion Failed! ')
978 for key in self.pool._sql_error.keys():
980 msg = self.pool._sql_error[key]
982 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '')
983 if isinstance(e, osv.orm.except_orm):
984 msg = _('Insertion Failed! ' + e[1])
985 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '')
986 #Raising Uncaught exception
987 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '')
989 for lang in translate:
990 context2 = context.copy()
991 context2['lang'] = lang
992 self.write(cr, uid, [id], translate[lang], context2)
993 if config.get('import_partial', False) and filename and (not (counter%100)):
994 data = pickle.load(file(config.get('import_partial')))
995 data[filename] = initial_size - len(datas) + original_value
996 pickle.dump(data, file(config.get('import_partial'), 'wb'))
997 if context.get('defer_parent_store_computation'):
998 self._parent_store_compute(cr)
1001 #except Exception, e:
1002 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1005 # return (-1, res, e[0], warning)
1007 # return (-1, res, e[0], '')
1010 # TODO: Send a request with the result and multi-thread !
1012 if context.get('defer_parent_store_computation'):
1013 self._parent_store_compute(cr)
1014 return (done, 0, 0, 0)
1016 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1018 Read records with given ids with the given fields
1020 :param cr: database cursor
1021 :param user: current user id
1022 :param ids: id or list of the ids of the records to read
1023 :param fields: optional list of field names to return (default: all fields would be returned)
1024 :type fields: list (example ['field_name_1', ...])
1025 :param context: optional context dictionary - it may contains keys for specifying certain options
1026 like ``context_lang``, ``context_tz`` to alter the results of the call.
1027 A special ``bin_size`` boolean flag may also be passed in the context to request the
1028 value of all fields.binary columns to be returned as the size of the binary instead of its
1029 contents. This can also be selectively overriden by passing a field-specific flag
1030 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1031 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1032 :return: list of dictionaries((dictionary per record asked)) with requested field values
1033 :rtype: [{‘name_of_the_field’: value, ...}, ...]
1034 :raise AccessError: * if user has no read rights on the requested object
1035 * if user tries to bypass access rules for read on the requested object
1038 raise NotImplementedError(_('The read method is not implemented on this object !'))
1040 def get_invalid_fields(self, cr, uid):
1041 return list(self._invalids)
1043 def _validate(self, cr, uid, ids, context=None):
1044 context = context or {}
1045 lng = context.get('lang', False) or 'en_US'
1046 trans = self.pool.get('ir.translation')
1048 for constraint in self._constraints:
1049 fun, msg, fields = constraint
1050 if not fun(self, cr, uid, ids):
1051 # Check presence of __call__ directly instead of using
1052 # callable() because it will be deprecated as of Python 3.0
1053 if hasattr(msg, '__call__'):
1054 txt_msg, params = msg(self, cr, uid, ids)
1055 tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg
1056 translated_msg = tmp_msg % params
1058 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1060 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1062 self._invalids.update(fields)
1065 raise except_orm('ValidateError', '\n'.join(error_msgs))
1067 self._invalids.clear()
1069 def default_get(self, cr, uid, fields_list, context=None):
1071 Returns default values for the fields in fields_list.
1073 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1074 :type fields_list: list
1075 :param context: optional context dictionary - it may contains keys for specifying certain options
1076 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1077 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1078 or override a default value for a field.
1079 A special ``bin_size`` boolean flag may also be passed in the context to request the
1080 value of all fields.binary columns to be returned as the size of the binary instead of its
1081 contents. This can also be selectively overriden by passing a field-specific flag
1082 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1083 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1084 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1086 # trigger view init hook
1087 self.view_init(cr, uid, fields_list, context)
1093 # get the default values for the inherited fields
1094 for t in self._inherits.keys():
1095 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1098 # get the default values defined in the object
1099 for f in fields_list:
1100 if f in self._defaults:
1101 if callable(self._defaults[f]):
1102 defaults[f] = self._defaults[f](self, cr, uid, context)
1104 defaults[f] = self._defaults[f]
1106 fld_def = ((f in self._columns) and self._columns[f]) \
1107 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1110 if isinstance(fld_def, fields.property):
1111 property_obj = self.pool.get('ir.property')
1112 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1114 if isinstance(prop_value, (browse_record, browse_null)):
1115 defaults[f] = prop_value.id
1117 defaults[f] = prop_value
1119 if f not in defaults:
1122 # get the default values set by the user and override the default
1123 # values defined in the object
1124 ir_values_obj = self.pool.get('ir.values')
1125 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1126 for id, field, field_value in res:
1127 if field in fields_list:
1128 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1129 if fld_def._type in ('many2one', 'one2one'):
1130 obj = self.pool.get(fld_def._obj)
1131 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1133 if fld_def._type in ('many2many'):
1134 obj = self.pool.get(fld_def._obj)
1136 for i in range(len(field_value)):
1137 if not obj.search(cr, uid, [('id', '=',
1140 field_value2.append(field_value[i])
1141 field_value = field_value2
1142 if fld_def._type in ('one2many'):
1143 obj = self.pool.get(fld_def._obj)
1145 for i in range(len(field_value)):
1146 field_value2.append({})
1147 for field2 in field_value[i]:
1148 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1149 obj2 = self.pool.get(obj._columns[field2]._obj)
1150 if not obj2.search(cr, uid,
1151 [('id', '=', field_value[i][field2])]):
1153 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1154 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1155 if not obj2.search(cr, uid,
1156 [('id', '=', field_value[i][field2])]):
1158 # TODO add test for many2many and one2many
1159 field_value2[i][field2] = field_value[i][field2]
1160 field_value = field_value2
1161 defaults[field] = field_value
1163 # get the default values from the context
1164 for key in context or {}:
1165 if key.startswith('default_') and (key[8:] in fields_list):
1166 defaults[key[8:]] = context[key]
1170 def perm_read(self, cr, user, ids, context=None, details=True):
1171 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1173 def unlink(self, cr, uid, ids, context=None):
1174 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1176 def write(self, cr, user, ids, vals, context=None):
1177 raise NotImplementedError(_('The write method is not implemented on this object !'))
1179 def create(self, cr, user, vals, context=None):
1180 raise NotImplementedError(_('The create method is not implemented on this object !'))
1182 def fields_get_keys(self, cr, user, context=None):
1183 res = self._columns.keys()
1184 for parent in self._inherits:
1185 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1188 # returns the definition of each field in the object
1189 # the optional fields parameter can limit the result to some fields
1190 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1194 translation_obj = self.pool.get('ir.translation')
1195 for parent in self._inherits:
1196 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1198 if self._columns.keys():
1199 for f in self._columns.keys():
1200 if allfields and f not in allfields:
1202 res[f] = {'type': self._columns[f]._type}
1203 # This additional attributes for M2M and function field is added
1204 # because we need to display tooltip with this additional information
1205 # when client is started in debug mode.
1206 if isinstance(self._columns[f], fields.function):
1207 res[f]['function'] = self._columns[f]._fnct and self._columns[f]._fnct.func_name or False
1208 res[f]['store'] = self._columns[f].store
1209 if isinstance(self._columns[f].store, dict):
1210 res[f]['store'] = str(self._columns[f].store)
1211 res[f]['fnct_search'] = self._columns[f]._fnct_search and self._columns[f]._fnct_search.func_name or False
1212 res[f]['fnct_inv'] = self._columns[f]._fnct_inv and self._columns[f]._fnct_inv.func_name or False
1213 res[f]['fnct_inv_arg'] = self._columns[f]._fnct_inv_arg or False
1214 res[f]['func_obj'] = self._columns[f]._obj or False
1215 res[f]['func_method'] = self._columns[f]._method
1216 if isinstance(self._columns[f], fields.many2many):
1217 res[f]['related_columns'] = list((self._columns[f]._id1, self._columns[f]._id2))
1218 res[f]['third_table'] = self._columns[f]._rel
1219 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1220 'change_default', 'translate', 'help', 'select', 'selectable'):
1221 if getattr(self._columns[f], arg):
1222 res[f][arg] = getattr(self._columns[f], arg)
1223 if not write_access:
1224 res[f]['readonly'] = True
1225 res[f]['states'] = {}
1226 for arg in ('digits', 'invisible', 'filters'):
1227 if getattr(self._columns[f], arg, None):
1228 res[f][arg] = getattr(self._columns[f], arg)
1230 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', self._columns[f].string)
1232 res[f]['string'] = res_trans
1233 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1235 res[f]['help'] = help_trans
1237 if hasattr(self._columns[f], 'selection'):
1238 if isinstance(self._columns[f].selection, (tuple, list)):
1239 sel = self._columns[f].selection
1240 # translate each selection option
1242 for (key, val) in sel:
1245 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1246 sel2.append((key, val2 or val))
1248 res[f]['selection'] = sel
1250 # call the 'dynamic selection' function
1251 res[f]['selection'] = self._columns[f].selection(self, cr,
1253 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1254 res[f]['relation'] = self._columns[f]._obj
1255 res[f]['domain'] = self._columns[f]._domain
1256 res[f]['context'] = self._columns[f]._context
1258 #TODO : read the fields from the database
1262 # filter out fields which aren't in the fields list
1263 for r in res.keys():
1264 if r not in allfields:
1269 # Overload this method if you need a window title which depends on the context
1271 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1274 def __view_look_dom(self, cr, user, node, view_id, context=None):
1282 if isinstance(s, unicode):
1283 return s.encode('utf8')
1286 # return True if node can be displayed to current user
1287 def check_group(node):
1288 if node.get('groups'):
1289 groups = node.get('groups').split(',')
1290 access_pool = self.pool.get('ir.model.access')
1291 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1293 node.set('invisible', '1')
1294 if 'attrs' in node.attrib:
1295 del(node.attrib['attrs']) #avoid making field visible later
1296 del(node.attrib['groups'])
1301 if node.tag in ('field', 'node', 'arrow'):
1302 if node.get('object'):
1307 if f.tag in ('field'):
1308 xml += etree.tostring(f, encoding="utf-8")
1310 new_xml = etree.fromstring(encode(xml))
1311 ctx = context.copy()
1312 ctx['base_model_name'] = self._name
1313 xarch, xfields = self.pool.get(node.get('object', False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1314 views[str(f.tag)] = {
1318 attrs = {'views': views}
1319 fields = views.get('field', False) and views['field'].get('fields', False)
1320 if node.get('name'):
1323 if node.get('name') in self._columns:
1324 column = self._columns[node.get('name')]
1326 column = self._inherit_fields[node.get('name')][2]
1331 relation = self.pool.get(column._obj)
1336 if f.tag in ('form', 'tree', 'graph'):
1338 ctx = context.copy()
1339 ctx['base_model_name'] = self._name
1340 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1341 views[str(f.tag)] = {
1345 attrs = {'views': views}
1346 if node.get('widget') and node.get('widget') == 'selection':
1347 # Prepare the cached selection list for the client. This needs to be
1348 # done even when the field is invisible to the current user, because
1349 # other events could need to change its value to any of the selectable ones
1350 # (such as on_change events, refreshes, etc.)
1352 # If domain and context are strings, we keep them for client-side, otherwise
1353 # we evaluate them server-side to consider them when generating the list of
1355 # TODO: find a way to remove this hack, by allow dynamic domains
1357 if column._domain and not isinstance(column._domain, basestring):
1358 dom = column._domain
1359 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1360 search_context = dict(context)
1361 if column._context and not isinstance(column._context, basestring):
1362 search_context.update(column._context)
1363 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1364 if (node.get('required') and not int(node.get('required'))) or not column.required:
1365 attrs['selection'].append((False, ''))
1366 fields[node.get('name')] = attrs
1368 elif node.tag in ('form', 'tree'):
1369 result = self.view_header_get(cr, user, False, node.tag, context)
1371 node.set('string', result)
1373 elif node.tag == 'calendar':
1374 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1375 if node.get(additional_field):
1376 fields[node.get(additional_field)] = {}
1378 if 'groups' in node.attrib:
1382 if ('lang' in context) and not result:
1383 if node.get('string'):
1384 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1385 if not trans and ('base_model_name' in context):
1386 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1388 node.set('string', trans)
1389 if node.get('confirm'):
1390 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm').encode('utf8'))
1391 if not trans and ('base_model_name' in context):
1392 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('confirm').encode('utf8'))
1394 node.set('confirm', trans)
1396 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1398 node.set('sum', trans)
1401 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1402 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1406 def _disable_workflow_buttons(self, cr, user, node):
1408 # admin user can always activate workflow buttons
1411 # TODO handle the case of more than one workflow for a model or multiple
1412 # transitions with different groups and same signal
1413 usersobj = self.pool.get('res.users')
1414 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1415 for button in buttons:
1416 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1417 cr.execute("""SELECT DISTINCT t.group_id
1419 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1420 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1423 AND t.group_id is NOT NULL
1424 """, (self._name, button.get('name')))
1425 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1426 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1427 button.set('readonly', str(int(not can_click)))
1430 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1431 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1432 node = self._disable_workflow_buttons(cr, user, node)
1433 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1435 if node.tag == 'diagram':
1436 if node.getchildren()[0].tag == 'node':
1437 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1438 if node.getchildren()[1].tag == 'arrow':
1439 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1440 for key, value in node_fields.items():
1442 for key, value in arrow_fields.items():
1445 fields = self.fields_get(cr, user, fields_def.keys(), context)
1446 for field in fields_def:
1448 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1449 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1450 elif field in fields:
1451 fields[field].update(fields_def[field])
1453 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))
1454 res = cr.fetchall()[:]
1456 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1457 msg = "\n * ".join([r[0] for r in res])
1458 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1459 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1460 raise except_orm('View error', msg)
1463 def __get_default_calendar_view(self):
1464 """Generate a default calendar view (For internal use only).
1467 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1468 '<calendar string="%s"') % (self._description)
1470 if (self._date_name not in self._columns):
1472 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1473 if dt in self._columns:
1474 self._date_name = dt
1479 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1482 arch += ' date_start="%s"' % (self._date_name)
1484 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1485 if color in self._columns:
1486 arch += ' color="' + color + '"'
1489 dt_stop_flag = False
1491 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1492 if dt_stop in self._columns:
1493 arch += ' date_stop="' + dt_stop + '"'
1497 if not dt_stop_flag:
1498 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1499 if dt_delay in self._columns:
1500 arch += ' date_delay="' + dt_delay + '"'
1504 ' <field name="%s"/>\n'
1505 '</calendar>') % (self._rec_name)
1509 def __get_default_search_view(self, cr, uid, context=None):
1512 if isinstance(s, unicode):
1513 return s.encode('utf8')
1516 view = self.fields_view_get(cr, uid, False, 'form', context=context)
1518 root = etree.fromstring(encode(view['arch']))
1519 res = etree.XML("""<search string="%s"></search>""" % root.get("string", ""))
1520 node = etree.Element("group")
1523 fields = root.xpath("//field[@select=1]")
1524 for field in fields:
1527 return etree.tostring(res, encoding="utf-8").replace('\t', '')
1530 # if view_id, view_type is not required
1532 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1534 Get the detailed composition of the requested view like fields, model, view architecture
1536 :param cr: database cursor
1537 :param user: current user id
1538 :param view_id: id of the view or None
1539 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1540 :param context: context arguments, like lang, time zone
1541 :param toolbar: true to include contextual actions
1542 :param submenu: example (portal_project module)
1543 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1544 :raise AttributeError:
1545 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1546 * if some tag other than 'position' is found in parent view
1547 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1554 if isinstance(s, unicode):
1555 return s.encode('utf8')
1558 def _inherit_apply(src, inherit):
1559 def _find(node, node2):
1560 if node2.tag == 'xpath':
1561 res = node.xpath(node2.get('expr'))
1567 for n in node.getiterator(node2.tag):
1569 if node2.tag == 'field':
1570 # only compare field names, a field can be only once in a given view
1571 # at a given level (and for multilevel expressions, we should use xpath
1572 # inheritance spec anyway)
1573 if node2.get('name') == n.get('name'):
1577 for attr in node2.attrib:
1578 if attr == 'position':
1581 if n.get(attr) == node2.get(attr):
1588 # End: _find(node, node2)
1590 doc_dest = etree.fromstring(encode(inherit))
1591 toparse = [doc_dest]
1594 node2 = toparse.pop(0)
1595 if node2.tag == 'data':
1596 toparse += [ c for c in doc_dest ]
1598 node = _find(src, node2)
1599 if node is not None:
1601 if node2.get('position'):
1602 pos = node2.get('position')
1603 if pos == 'replace':
1604 parent = node.getparent()
1606 src = copy.deepcopy(node2[0])
1609 node.addprevious(child)
1610 node.getparent().remove(node)
1611 elif pos == 'attributes':
1612 for child in node2.getiterator('attribute'):
1613 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1615 node.set(attribute[0], attribute[1])
1617 del(node.attrib[attribute[0]])
1619 sib = node.getnext()
1623 elif pos == 'after':
1627 sib.addprevious(child)
1628 elif pos == 'before':
1629 node.addprevious(child)
1631 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1634 ' %s="%s"' % (attr, node2.get(attr))
1635 for attr in node2.attrib
1636 if attr != 'position'
1638 tag = "<%s%s>" % (node2.tag, attrs)
1639 raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1641 # End: _inherit_apply(src, inherit)
1643 result = {'type': view_type, 'model': self._name}
1649 view_ref = context.get(view_type + '_view_ref', False)
1650 if view_ref and not view_id:
1652 module, view_ref = view_ref.split('.', 1)
1653 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1654 view_ref_res = cr.fetchone()
1656 view_id = view_ref_res[0]
1659 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1662 query += " AND model=%s"
1663 params += (self._name,)
1664 cr.execute(query, params)
1666 cr.execute('''SELECT
1667 arch,name,field_parent,id,type,inherit_id
1674 ORDER BY priority''', (self._name, view_type))
1675 sql_res = cr.fetchone()
1681 view_id = ok or sql_res[3]
1684 # if a view was found
1686 result['type'] = sql_res[4]
1687 result['view_id'] = sql_res[3]
1688 result['arch'] = sql_res[0]
1690 def _inherit_apply_rec(result, inherit_id):
1691 # get all views which inherit from (ie modify) this view
1692 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1693 sql_inherit = cr.fetchall()
1694 for (inherit, id) in sql_inherit:
1695 result = _inherit_apply(result, inherit)
1696 result = _inherit_apply_rec(result, id)
1699 inherit_result = etree.fromstring(encode(result['arch']))
1700 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1702 result['name'] = sql_res[1]
1703 result['field_parent'] = sql_res[2] or False
1706 # otherwise, build some kind of default view
1707 if view_type == 'form':
1708 res = self.fields_get(cr, user, context=context)
1709 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1710 '<form string="%s">' % (self._description,)
1712 if res[x]['type'] not in ('one2many', 'many2many'):
1713 xml += '<field name="%s"/>' % (x,)
1714 if res[x]['type'] == 'text':
1718 elif view_type == 'tree':
1719 _rec_name = self._rec_name
1720 if _rec_name not in self._columns:
1721 _rec_name = self._columns.keys()[0]
1722 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1723 '<tree string="%s"><field name="%s"/></tree>' \
1724 % (self._description, self._rec_name)
1726 elif view_type == 'calendar':
1727 xml = self.__get_default_calendar_view()
1729 elif view_type == 'search':
1730 xml = self.__get_default_search_view(cr, user, context)
1733 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1734 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1735 result['arch'] = etree.fromstring(encode(xml))
1736 result['name'] = 'default'
1737 result['field_parent'] = False
1738 result['view_id'] = 0
1740 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1741 result['arch'] = xarch
1742 result['fields'] = xfields
1745 if context and context.get('active_id', False):
1746 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1748 act_id = data_menu.id
1750 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1751 result['submenu'] = getattr(data_action, 'menus', False)
1755 for key in ('report_sxw_content', 'report_rml_content',
1756 'report_sxw', 'report_rml',
1757 'report_sxw_content_data', 'report_rml_content_data'):
1761 ir_values_obj = self.pool.get('ir.values')
1762 resprint = ir_values_obj.get(cr, user, 'action',
1763 'client_print_multi', [(self._name, False)], False,
1765 resaction = ir_values_obj.get(cr, user, 'action',
1766 'client_action_multi', [(self._name, False)], False,
1769 resrelate = ir_values_obj.get(cr, user, 'action',
1770 'client_action_relate', [(self._name, False)], False,
1772 resprint = map(clean, resprint)
1773 resaction = map(clean, resaction)
1774 resaction = filter(lambda x: not x.get('multi', False), resaction)
1775 resprint = filter(lambda x: not x.get('multi', False), resprint)
1776 resrelate = map(lambda x: x[2], resrelate)
1778 for x in resprint + resaction + resrelate:
1779 x['string'] = x['name']
1781 result['toolbar'] = {
1783 'action': resaction,
1788 _view_look_dom_arch = __view_look_dom_arch
1790 def search_count(self, cr, user, args, context=None):
1793 res = self.search(cr, user, args, context=context, count=True)
1794 if isinstance(res, list):
1798 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1800 Search for records based on a search domain.
1802 :param cr: database cursor
1803 :param user: current user id
1804 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1805 :param offset: optional number of results to skip in the returned values (default: 0)
1806 :param limit: optional max number of records to return (default: **None**)
1807 :param order: optional columns to sort by (default: self._order=id )
1808 :param context: optional context arguments, like lang, time zone
1809 :type context: dictionary
1810 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1811 :return: id or list of ids of records matching the criteria
1812 :rtype: integer or list of integers
1813 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1815 **Expressing a search domain (args)**
1817 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1819 * **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.
1820 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1821 The semantics of most of these operators are obvious.
1822 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1823 according to the semantics of this model (i.e following the relationship field named by
1824 ``self._parent_name``, by default ``parent_id``.
1825 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1827 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1828 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1829 Be very careful about this when you combine them the first time.
1831 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1833 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1835 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::
1837 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1840 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1842 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1844 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1845 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1846 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1848 :param access_rights_uid: optional user ID to use when checking access rights
1849 (not for ir.rules, this is only for ir.model.access)
1851 raise NotImplementedError(_('The search method is not implemented on this object !'))
1853 def name_get(self, cr, user, ids, context=None):
1856 :param cr: database cursor
1857 :param user: current user id
1859 :param ids: list of ids
1860 :param context: context arguments, like lang, time zone
1861 :type context: dictionary
1862 :return: tuples with the text representation of requested objects for to-many relationships
1869 if isinstance(ids, (int, long)):
1871 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1872 [self._rec_name], context, load='_classic_write')]
1874 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1876 Search for records and their display names according to a search domain.
1878 :param cr: database cursor
1879 :param user: current user id
1880 :param name: object name to search
1881 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1882 :param operator: operator for search criterion
1883 :param context: context arguments, like lang, time zone
1884 :type context: dictionary
1885 :param limit: optional max number of records to return
1886 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1888 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1889 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1892 return self._name_search(cr, user, name, args, operator, context, limit)
1894 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1895 # solve some access rights issues
1896 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1903 args += [(self._rec_name, operator, name)]
1904 access_rights_uid = name_get_uid or user
1905 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1906 res = self.name_get(cr, access_rights_uid, ids, context)
1909 def copy(self, cr, uid, id, default=None, context=None):
1910 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1912 def exists(self, cr, uid, id, context=None):
1913 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1915 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1918 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1920 fields = self._columns.keys() + self._inherit_fields.keys()
1921 #FIXME: collect all calls to _get_source into one SQL call.
1923 res[lang] = {'code': lang}
1925 if f in self._columns:
1926 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1928 res[lang][f] = res_trans
1930 res[lang][f] = self._columns[f].string
1931 for table in self._inherits:
1932 cols = intersect(self._inherit_fields.keys(), fields)
1933 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1936 res[lang]['code'] = lang
1937 for f in res2[lang]:
1938 res[lang][f] = res2[lang][f]
1941 def write_string(self, cr, uid, id, langs, vals, context=None):
1942 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1943 #FIXME: try to only call the translation in one SQL
1946 if field in self._columns:
1947 src = self._columns[field].string
1948 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1949 for table in self._inherits:
1950 cols = intersect(self._inherit_fields.keys(), vals)
1952 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1955 def _check_removed_columns(self, cr, log=False):
1956 raise NotImplementedError()
1958 def _add_missing_default_values(self, cr, uid, values, context=None):
1959 missing_defaults = []
1960 avoid_tables = [] # avoid overriding inherited values when parent is set
1961 for tables, parent_field in self._inherits.items():
1962 if parent_field in values:
1963 avoid_tables.append(tables)
1964 for field in self._columns.keys():
1965 if not field in values:
1966 missing_defaults.append(field)
1967 for field in self._inherit_fields.keys():
1968 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1969 missing_defaults.append(field)
1971 if len(missing_defaults):
1972 # override defaults with the provided values, never allow the other way around
1973 defaults = self.default_get(cr, uid, missing_defaults, context)
1975 if (dv in self._columns and self._columns[dv]._type == 'many2many') \
1976 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many') \
1977 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1978 defaults[dv] = [(6, 0, defaults[dv])]
1979 if dv in self._columns and self._columns[dv]._type == 'one2many' \
1980 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many') \
1981 and isinstance(defaults[dv], (list, tuple)) and isinstance(defaults[dv][0], dict):
1982 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
1983 defaults.update(values)
1987 class orm_memory(orm_template):
1989 _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']
1990 _inherit_fields = {}
1995 def __init__(self, cr):
1996 super(orm_memory, self).__init__(cr)
2000 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2002 def _check_access(self, uid, object_id, mode):
2003 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2004 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2006 def vaccum(self, cr, uid):
2008 if self.check_id % self._check_time:
2011 max = time.time() - self._max_hours * 60 * 60
2012 for id in self.datas:
2013 if self.datas[id]['internal.date_access'] < max:
2015 self.unlink(cr, 1, tounlink)
2016 if len(self.datas) > self._max_count:
2017 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
2019 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
2020 self.unlink(cr, uid, ids)
2023 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2026 if not fields_to_read:
2027 fields_to_read = self._columns.keys()
2031 if isinstance(ids, (int, long)):
2035 for f in fields_to_read:
2036 record = self.datas.get(id)
2038 self._check_access(user, id, 'read')
2039 r[f] = record.get(f, False)
2040 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2043 if id in self.datas:
2044 self.datas[id]['internal.date_access'] = time.time()
2045 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2046 for f in fields_post:
2047 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2048 for record in result:
2049 record[f] = res2[record['id']]
2050 if isinstance(ids_orig, (int, long)):
2054 def write(self, cr, user, ids, vals, context=None):
2060 if self._columns[field]._classic_write:
2061 vals2[field] = vals[field]
2063 upd_todo.append(field)
2064 for object_id in ids:
2065 self._check_access(user, object_id, mode='write')
2066 self.datas[object_id].update(vals2)
2067 self.datas[object_id]['internal.date_access'] = time.time()
2068 for field in upd_todo:
2069 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2070 self._validate(cr, user, [object_id], context)
2071 wf_service = netsvc.LocalService("workflow")
2072 wf_service.trg_write(user, self._name, object_id, cr)
2075 def create(self, cr, user, vals, context=None):
2076 self.vaccum(cr, user)
2078 id_new = self.next_id
2080 vals = self._add_missing_default_values(cr, user, vals, context)
2085 if self._columns[field]._classic_write:
2086 vals2[field] = vals[field]
2088 upd_todo.append(field)
2089 self.datas[id_new] = vals2
2090 self.datas[id_new]['internal.date_access'] = time.time()
2091 self.datas[id_new]['internal.create_uid'] = user
2093 for field in upd_todo:
2094 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2095 self._validate(cr, user, [id_new], context)
2096 if self._log_create and not (context and context.get('no_store_function', False)):
2097 message = self._description + \
2099 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2101 self.log(cr, user, id_new, message, True, context=context)
2102 wf_service = netsvc.LocalService("workflow")
2103 wf_service.trg_create(user, self._name, id_new, cr)
2106 def _where_calc(self, cr, user, args, active_test=True, context=None):
2111 # if the object has a field named 'active', filter out all inactive
2112 # records unless they were explicitely asked for
2113 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2115 active_in_args = False
2117 if a[0] == 'active':
2118 active_in_args = True
2119 if not active_in_args:
2120 args.insert(0, ('active', '=', 1))
2122 args = [('active', '=', 1)]
2125 e = expression.expression(args)
2126 e.parse(cr, user, self, context)
2130 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2134 # implicit filter on current user except for superuser
2138 args.insert(0, ('internal.create_uid', '=', user))
2140 result = self._where_calc(cr, user, args, context=context)
2142 return self.datas.keys()
2146 #Find the value of dict
2149 for id, data in self.datas.items():
2150 counter = counter + 1
2152 if limit and (counter > int(limit)):
2157 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2158 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2159 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2160 elif arg[1] in ['ilike']:
2161 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2171 def unlink(self, cr, uid, ids, context=None):
2173 self._check_access(uid, id, 'unlink')
2174 self.datas.pop(id, None)
2176 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2179 def perm_read(self, cr, user, ids, context=None, details=True):
2181 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2182 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2184 self._check_access(user, id, 'read')
2186 'create_uid': credentials,
2187 'create_date': create_date,
2189 'write_date': False,
2195 def _check_removed_columns(self, cr, log=False):
2196 # nothing to check in memory...
2199 def exists(self, cr, uid, id, context=None):
2200 return id in self.datas
2202 class orm(orm_template):
2203 _sql_constraints = []
2205 _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']
2206 __logger = logging.getLogger('orm')
2207 __schema = logging.getLogger('orm.schema')
2208 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2210 Get the list of records in list view grouped by the given ``groupby`` fields
2212 :param cr: database cursor
2213 :param uid: current user id
2214 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2215 :param fields: list of fields present in the list view specified on the object
2216 :param groupby: list of fields on which to groupby the records
2217 :type fields_list: list (example ['field_name_1', ...])
2218 :param offset: optional number of records to skip
2219 :param limit: optional max number of records to return
2220 :param context: context arguments, like lang, time zone
2221 :return: list of dictionaries(one dictionary for each record) containing:
2223 * the values of fields grouped by the fields in ``groupby`` argument
2224 * __domain: list of tuples specifying the search criteria
2225 * __context: dictionary with argument like ``groupby``
2226 :rtype: [{'field_name_1': value, ...]
2227 :raise AccessError: * if user has no read rights on the requested object
2228 * if user tries to bypass access rules for read on the requested object
2231 context = context or {}
2232 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2234 fields = self._columns.keys()
2236 query = self._where_calc(cr, uid, domain, context=context)
2237 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2239 # Take care of adding join(s) if groupby is an '_inherits'ed field
2240 groupby_list = groupby
2242 if isinstance(groupby, list):
2243 groupby = groupby[0]
2244 self._inherits_join_calc(groupby, query)
2246 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?)"
2248 fget = self.fields_get(cr, uid, fields)
2249 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2253 if fget.get(groupby):
2254 if fget[groupby]['type'] in ('date', 'datetime'):
2255 flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
2256 groupby = "to_char(%s,'yyyy-mm')" % (groupby)
2260 # Don't allow arbitrary values, as this would be a SQL injection vector!
2261 raise except_orm(_('Invalid group_by'),
2262 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2265 fields_pre = [f for f in float_int_fields if
2266 f == self.CONCURRENCY_CHECK_FIELD
2267 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2268 for f in fields_pre:
2269 if f not in ['id', 'sequence']:
2270 group_operator = fget[f].get('group_operator', 'sum')
2273 flist += group_operator+'('+f+') as '+f
2275 gb = groupby and (' GROUP BY '+groupby) or ''
2277 from_clause, where_clause, where_clause_params = query.get_sql()
2278 where_clause = where_clause and ' WHERE ' + where_clause
2279 limit_str = limit and ' limit %d' % limit or ''
2280 offset_str = offset and ' offset %d' % offset or ''
2281 cr.execute('SELECT min(%s.id) AS id,' % self._table + flist + ' FROM ' + from_clause + where_clause + gb + limit_str + offset_str, where_clause_params)
2284 for r in cr.dictfetchall():
2285 for fld, val in r.items():
2286 if val == None: r[fld] = False
2287 alldata[r['id']] = r
2289 if groupby and fget[groupby]['type'] == 'many2one':
2290 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=groupby, context=context)
2291 # the IDS of the records that has groupby field value = False or ''
2292 # should be added too
2293 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2294 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2295 # 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):
2296 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2298 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2300 data.sort(lambda x,y:cmp(x[groupby],y[groupby]))
2303 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2304 if not isinstance(groupby_list, (str, unicode)):
2305 if groupby or not context.get('group_by_no_leaf', False):
2306 d['__context'] = {'group_by': groupby_list[1:]}
2307 if groupby and groupby in fget:
2308 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2309 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2310 days = calendar.monthrange(dt.year, dt.month)[1]
2312 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2313 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),\
2314 (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
2315 del alldata[d['id']][groupby]
2316 d.update(alldata[d['id']])
2320 def _inherits_join_add(self, parent_model_name, query):
2322 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2324 :param parent_model_name: name of the parent model for which the clauses should be added
2325 :param query: query object on which the JOIN should be added
2327 inherits_field = self._inherits[parent_model_name]
2328 parent_model = self.pool.get(parent_model_name)
2329 parent_table_name = parent_model._table
2330 quoted_parent_table_name = '"%s"' % parent_table_name
2331 if quoted_parent_table_name not in query.tables:
2332 query.tables.append(quoted_parent_table_name)
2333 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2335 def _inherits_join_calc(self, field, query):
2337 Adds missing table select and join clause(s) to ``query`` for reaching
2338 the field coming from an '_inherits' parent table (no duplicates).
2340 :param field: name of inherited field to reach
2341 :param query: query object on which the JOIN should be added
2342 :return: qualified name of field, to be used in SELECT clause
2344 current_table = self
2345 while field in current_table._inherit_fields and not field in current_table._columns:
2346 parent_model_name = current_table._inherit_fields[field][0]
2347 parent_table = self.pool.get(parent_model_name)
2348 self._inherits_join_add(parent_model_name, query)
2349 current_table = parent_table
2350 return '"%s".%s' % (current_table._table, field)
2352 def _parent_store_compute(self, cr):
2353 if not self._parent_store:
2355 logger = netsvc.Logger()
2356 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2357 def browse_rec(root, pos=0):
2359 where = self._parent_name+'='+str(root)
2361 where = self._parent_name+' IS NULL'
2362 if self._parent_order:
2363 where += ' order by '+self._parent_order
2364 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2366 for id in cr.fetchall():
2367 pos2 = browse_rec(id[0], pos2)
2368 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2370 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2371 if self._parent_order:
2372 query += ' order by ' + self._parent_order
2375 for (root,) in cr.fetchall():
2376 pos = browse_rec(root, pos)
2379 def _update_store(self, cr, f, k):
2380 logger = netsvc.Logger()
2381 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2382 ss = self._columns[k]._symbol_set
2383 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2384 cr.execute('select id from '+self._table)
2385 ids_lst = map(lambda x: x[0], cr.fetchall())
2388 ids_lst = ids_lst[40:]
2389 res = f.get(cr, self, iids, k, 1, {})
2390 for key, val in res.items():
2393 # if val is a many2one, just write the ID
2394 if type(val) == tuple:
2396 if (val<>False) or (type(val)<>bool):
2397 cr.execute(update_query, (ss[1](val), key))
2399 def _check_removed_columns(self, cr, log=False):
2400 # iterate on the database columns to drop the NOT NULL constraints
2401 # of fields which were required but have been removed (or will be added by another module)
2402 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2403 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2404 cr.execute("SELECT a.attname, a.attnotnull"
2405 " FROM pg_class c, pg_attribute a"
2406 " WHERE c.relname=%s"
2407 " AND c.oid=a.attrelid"
2408 " AND a.attisdropped=%s"
2409 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2410 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2412 for column in cr.dictfetchall():
2414 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2415 column['attname'], self._table, self._name)
2416 if column['attnotnull']:
2417 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2418 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2419 self._table, column['attname'])
2421 def _auto_init(self, cr, context=None):
2424 store_compute = False
2427 self._field_create(cr, context=context)
2428 if getattr(self, '_auto', True):
2429 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2431 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2432 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2434 self.__schema.debug("Table '%s': created", self._table)
2437 if self._parent_store:
2438 cr.execute("""SELECT c.relname
2439 FROM pg_class c, pg_attribute a
2440 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2441 """, (self._table, 'parent_left'))
2443 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2444 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2445 if 'parent_left' not in self._columns:
2446 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2448 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2449 self._table, 'parent_left', 'INTEGER')
2450 if 'parent_right' not in self._columns:
2451 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2453 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2454 self._table, 'parent_right', 'INTEGER')
2455 if self._columns[self._parent_name].ondelete != 'cascade':
2456 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2457 self._parent_name, self._name)
2460 store_compute = True
2462 if self._log_access:
2464 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2465 'create_date': 'TIMESTAMP',
2466 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2467 'write_date': 'TIMESTAMP'
2472 FROM pg_class c, pg_attribute a
2473 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2474 """, (self._table, k))
2476 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2478 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2479 self._table, k, logs[k])
2481 self._check_removed_columns(cr, log=False)
2483 # iterate on the "object columns"
2484 todo_update_store = []
2485 update_custom_fields = context.get('update_custom_fields', False)
2487 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 " \
2488 "FROM pg_class c,pg_attribute a,pg_type t " \
2489 "WHERE c.relname=%s " \
2490 "AND c.oid=a.attrelid " \
2491 "AND a.atttypid=t.oid", (self._table,))
2492 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2495 for k in self._columns:
2496 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2498 #Not Updating Custom fields
2499 if k.startswith('x_') and not update_custom_fields:
2502 f = self._columns[k]
2504 if isinstance(f, fields.one2many):
2505 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2507 if self.pool.get(f._obj):
2508 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2509 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2510 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2513 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))
2514 res = cr.fetchone()[0]
2516 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2517 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2518 self._obj, f._fields_id, f._table)
2519 elif isinstance(f, fields.many2many):
2520 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2521 if not cr.dictfetchall():
2522 if not self.pool.get(f._obj):
2523 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2524 ref = self.pool.get(f._obj)._table
2525 # ref = f._obj.replace('.', '_')
2526 cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE) WITH OIDS' % (f._rel, f._id1, self._table, f._id2, ref))
2527 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2528 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2529 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2531 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2532 f._rel, self._table, ref)
2534 res = col_data.get(k, [])
2535 res = res and [res] or []
2536 if not res and hasattr(f, 'oldname'):
2537 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 " \
2538 "FROM pg_class c,pg_attribute a,pg_type t " \
2539 "WHERE c.relname=%s " \
2540 "AND a.attname=%s " \
2541 "AND c.oid=a.attrelid " \
2542 "AND a.atttypid=t.oid", (self._table, f.oldname))
2543 res_old = cr.dictfetchall()
2544 if res_old and len(res_old) == 1:
2545 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2547 res[0]['attname'] = k
2548 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2549 self._table, f.oldname, k)
2553 f_pg_type = f_pg_def['typname']
2554 f_pg_size = f_pg_def['size']
2555 f_pg_notnull = f_pg_def['attnotnull']
2556 if isinstance(f, fields.function) and not f.store and\
2557 not getattr(f, 'nodrop', False):
2558 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2559 k, f.string, self._table)
2560 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2562 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2566 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2571 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2572 ('varchar', 'text', 'TEXT', ''),
2573 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2574 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2575 ('timestamp', 'date', 'date', '::date'),
2576 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2577 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2579 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2580 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2581 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2582 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2583 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2585 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2586 self._table, k, f_pg_size, f.size)
2588 if (f_pg_type==c[0]) and (f._type==c[1]):
2589 if f_pg_type != f_obj_type:
2591 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2592 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2593 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2594 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2596 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2597 self._table, k, c[0], c[1])
2600 if f_pg_type != f_obj_type:
2604 newname = self._table + '_moved' + str(i)
2605 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2606 "WHERE c.relname=%s " \
2607 "AND a.attname=%s " \
2608 "AND c.oid=a.attrelid ", (self._table, newname))
2609 if not cr.fetchone()[0]:
2613 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2614 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2615 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2616 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2617 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2618 self._table, k, f_pg_type, f._type, newname)
2620 # if the field is required and hasn't got a NOT NULL constraint
2621 if f.required and f_pg_notnull == 0:
2622 # set the field to the default value if any
2623 if k in self._defaults:
2624 if callable(self._defaults[k]):
2625 default = self._defaults[k](self, cr, 1, context)
2627 default = self._defaults[k]
2629 if (default is not None):
2630 ss = self._columns[k]._symbol_set
2631 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2632 cr.execute(query, (ss[1](default),))
2633 # add the NOT NULL constraint
2636 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2638 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2641 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2642 "If you want to have it, you should update the records and execute manually:\n"\
2643 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2644 self.__schema.warn(msg, self._table, k, self._table, k)
2646 elif not f.required and f_pg_notnull == 1:
2647 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2649 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2652 indexname = '%s_%s_index' % (self._table, k)
2653 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2654 res2 = cr.dictfetchall()
2655 if not res2 and f.select:
2656 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2658 if f._type == 'text':
2659 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2660 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2661 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2662 " because there is a length limit for indexable btree values!\n"\
2663 "Use a search view instead if you simply want to make the field searchable."
2664 self.__schema.warn(msg, self._table, k, f._type)
2665 if res2 and not f.select:
2666 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2668 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2669 self.__schema.warn(msg, self._table, k, f._type)
2671 if isinstance(f, fields.many2one):
2672 ref = self.pool.get(f._obj)._table
2673 if ref != 'ir_actions':
2674 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2675 'pg_attribute as att1, pg_attribute as att2 '
2676 'WHERE con.conrelid = cl1.oid '
2677 'AND cl1.relname = %s '
2678 'AND con.confrelid = cl2.oid '
2679 'AND cl2.relname = %s '
2680 'AND array_lower(con.conkey, 1) = 1 '
2681 'AND con.conkey[1] = att1.attnum '
2682 'AND att1.attrelid = cl1.oid '
2683 'AND att1.attname = %s '
2684 'AND array_lower(con.confkey, 1) = 1 '
2685 'AND con.confkey[1] = att2.attnum '
2686 'AND att2.attrelid = cl2.oid '
2687 'AND att2.attname = %s '
2688 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2689 res2 = cr.dictfetchall()
2691 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2692 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2693 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2695 self.__schema.debug("Table '%s': column '%s': XXX",
2698 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2700 if not isinstance(f, fields.function) or f.store:
2701 # add the missing field
2702 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2703 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2704 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2705 self._table, k, get_pg_type(f)[1])
2708 if not create and k in self._defaults:
2709 if callable(self._defaults[k]):
2710 default = self._defaults[k](self, cr, 1, context)
2712 default = self._defaults[k]
2714 ss = self._columns[k]._symbol_set
2715 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2716 cr.execute(query, (ss[1](default),))
2718 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2720 if isinstance(f, fields.function):
2722 if f.store is not True:
2723 order = f.store[f.store.keys()[0]][2]
2724 todo_update_store.append((order, f, k))
2726 # and add constraints if needed
2727 if isinstance(f, fields.many2one):
2728 if not self.pool.get(f._obj):
2729 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2730 ref = self.pool.get(f._obj)._table
2731 # ref = f._obj.replace('.', '_')
2732 # ir_actions is inherited so foreign key doesn't work on it
2733 if ref != 'ir_actions':
2734 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2735 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2736 self._table, k, ref, f.ondelete)
2738 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2742 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2743 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2746 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2747 "Try to re-run: openerp-server.py --update=module\n"\
2748 "If it doesn't work, update records and execute manually:\n"\
2749 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2750 self.__logger.warn(msg, k, self._table, self._table, k)
2752 for order, f, k in todo_update_store:
2753 todo_end.append((order, self._update_store, (f, k)))
2756 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2757 create = not bool(cr.fetchone())
2759 cr.commit() # start a new transaction
2761 for (key, con, _) in self._sql_constraints:
2762 conname = '%s_%s' % (self._table, key)
2764 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2765 existing_constraints = cr.dictfetchall()
2770 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2771 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2772 self._table, conname, con),
2773 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2778 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2779 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2780 '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" % (
2786 if not existing_constraints:
2787 # constraint does not exists:
2788 sql_actions['add']['execute'] = True
2789 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2790 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2791 # constraint exists but its definition has changed:
2792 sql_actions['drop']['execute'] = True
2793 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2794 sql_actions['add']['execute'] = True
2795 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2797 # we need to add the constraint:
2798 sql_actions = [item for item in sql_actions.values()]
2799 sql_actions.sort(key=lambda x: x['order'])
2800 for sql_action in [action for action in sql_actions if action['execute']]:
2802 cr.execute(sql_action['query'])
2804 self.__schema.debug(sql_action['msg_ok'])
2806 self.__schema.warn(sql_action['msg_err'])
2810 if hasattr(self, "_sql"):
2811 for line in self._sql.split(';'):
2812 line2 = line.replace('\n', '').strip()
2817 self._parent_store_compute(cr)
2821 def __init__(self, cr):
2822 super(orm, self).__init__(cr)
2824 if not hasattr(self, '_log_access'):
2825 # if not access is not specify, it is the same value as _auto
2826 self._log_access = getattr(self, "_auto", True)
2828 self._columns = self._columns.copy()
2829 for store_field in self._columns:
2830 f = self._columns[store_field]
2831 if hasattr(f, 'digits_change'):
2833 if not isinstance(f, fields.function):
2837 if self._columns[store_field].store is True:
2838 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2840 sm = self._columns[store_field].store
2841 for object, aa in sm.items():
2843 (fnct, fields2, order, length) = aa
2845 (fnct, fields2, order) = aa
2848 raise except_orm('Error',
2849 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2850 self.pool._store_function.setdefault(object, [])
2852 for x, y, z, e, f, l in self.pool._store_function[object]:
2853 if (x==self._name) and (y==store_field) and (e==fields2):
2857 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2858 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2860 for (key, _, msg) in self._sql_constraints:
2861 self.pool._sql_error[self._table+'_'+key] = msg
2863 # Load manual fields
2865 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2867 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2868 for field in cr.dictfetchall():
2869 if field['name'] in self._columns:
2872 'string': field['field_description'],
2873 'required': bool(field['required']),
2874 'readonly': bool(field['readonly']),
2875 'domain': field['domain'] or None,
2876 'size': field['size'],
2877 'ondelete': field['on_delete'],
2878 'translate': (field['translate']),
2879 #'select': int(field['select_level'])
2882 if field['ttype'] == 'selection':
2883 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2884 elif field['ttype'] == 'reference':
2885 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2886 elif field['ttype'] == 'many2one':
2887 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2888 elif field['ttype'] == 'one2many':
2889 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2890 elif field['ttype'] == 'many2many':
2891 _rel1 = field['relation'].replace('.', '_')
2892 _rel2 = field['model'].replace('.', '_')
2893 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2894 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2896 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2897 self._inherits_check()
2898 self._inherits_reload()
2899 if not self._sequence:
2900 self._sequence = self._table + '_id_seq'
2901 for k in self._defaults:
2902 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,)
2903 for f in self._columns:
2904 self._columns[f].restart()
2907 # Update objects that uses this one to update their _inherits fields
2910 def _inherits_reload_src(self):
2911 for obj in self.pool.obj_pool.values():
2912 if self._name in obj._inherits:
2913 obj._inherits_reload()
2915 def _inherits_reload(self):
2917 for table in self._inherits:
2918 res.update(self.pool.get(table)._inherit_fields)
2919 for col in self.pool.get(table)._columns.keys():
2920 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2921 for col in self.pool.get(table)._inherit_fields.keys():
2922 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2923 self._inherit_fields = res
2924 self._inherits_reload_src()
2926 def _inherits_check(self):
2927 for table, field_name in self._inherits.items():
2928 if field_name not in self._columns:
2929 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2930 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2931 required=True, ondelete="cascade")
2932 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2933 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))
2934 self._columns[field_name].required = True
2935 self._columns[field_name].ondelete = "cascade"
2937 #def __getattr__(self, name):
2939 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2940 # (though inherits doesn't use Python inheritance).
2941 # Handles translating between local ids and remote ids.
2942 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2943 # when you have inherits.
2945 # for model, field in self._inherits.iteritems():
2946 # proxy = self.pool.get(model)
2947 # if hasattr(proxy, name):
2948 # attribute = getattr(proxy, name)
2949 # if not hasattr(attribute, '__call__'):
2953 # return super(orm, self).__getattr__(name)
2955 # def _proxy(cr, uid, ids, *args, **kwargs):
2956 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2957 # lst = [obj[field].id for obj in objects if obj[field]]
2958 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2963 def fields_get(self, cr, user, fields=None, context=None):
2965 Get the description of list of fields
2967 :param cr: database cursor
2968 :param user: current user id
2969 :param fields: list of fields
2970 :param context: context arguments, like lang, time zone
2971 :return: dictionary of field dictionaries, each one describing a field of the business object
2972 :raise AccessError: * if user has no create/write rights on the requested object
2975 ira = self.pool.get('ir.model.access')
2976 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2977 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2978 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2980 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2983 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2985 fields = self._columns.keys() + self._inherit_fields.keys()
2986 if isinstance(ids, (int, long)):
2990 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
2991 result = self._read_flat(cr, user, select, fields, context, load)
2994 for key, v in r.items():
2998 if isinstance(ids, (int, long, dict)):
2999 return result and result[0] or False
3002 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
3007 if fields_to_read == None:
3008 fields_to_read = self._columns.keys()
3010 # Construct a clause for the security rules.
3011 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3012 # or will at least contain self._table.
3013 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3015 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
3016 fields_pre = [f for f in fields_to_read if
3017 f == self.CONCURRENCY_CHECK_FIELD
3018 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3019 ] + self._inherits.values()
3023 def convert_field(f):
3024 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
3025 if f in ('create_date', 'write_date'):
3026 return "date_trunc('second', %s) as %s" % (f_qual, f)
3027 if f == self.CONCURRENCY_CHECK_FIELD:
3028 if self._log_access:
3029 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
3030 return "now()::timestamp AS %s" % (f,)
3031 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
3032 return 'length(%s) as "%s"' % (f_qual, f)
3035 fields_pre2 = map(convert_field, fields_pre)
3036 order_by = self._parent_order or self._order
3037 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
3038 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3040 query += " AND " + (' OR '.join(rule_clause))
3041 query += " ORDER BY " + order_by
3042 for sub_ids in cr.split_for_in_conditions(ids):
3044 cr.execute(query, [tuple(sub_ids)] + rule_params)
3045 if cr.rowcount != len(sub_ids):
3046 raise except_orm(_('AccessError'),
3047 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3048 % (self._description,))
3050 cr.execute(query, (tuple(sub_ids),))
3051 res.extend(cr.dictfetchall())
3053 res = map(lambda x: {'id': x}, ids)
3056 # res = map(lambda x: {'id': x}, ids)
3057 # for record in res:
3058 # for f in fields_to_read:
3060 # if f in self._columns.keys():
3061 # ftype = self._columns[f]._type
3062 # elif f in self._inherit_fields.keys():
3063 # ftype = self._inherit_fields[f][2]._type
3066 # if ftype in ('one2many', 'many2many'):
3068 # record.update({f:field_val})
3070 for f in fields_pre:
3071 if f == self.CONCURRENCY_CHECK_FIELD:
3073 if self._columns[f].translate:
3074 ids = [x['id'] for x in res]
3075 #TODO: optimize out of this loop
3076 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3078 r[f] = res_trans.get(r['id'], False) or r[f]
3080 for table in self._inherits:
3081 col = self._inherits[table]
3082 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
3085 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3093 if not record[col]: # if the record is deleted from _inherits table?
3095 record.update(res3[record[col]])
3096 if col not in fields_to_read:
3099 # all fields which need to be post-processed by a simple function (symbol_get)
3100 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3103 for f in fields_post:
3104 r[f] = self._columns[f]._symbol_get(r[f])
3105 ids = [x['id'] for x in res]
3107 # all non inherited fields for which the attribute whose name is in load is False
3108 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3110 # Compute POST fields
3112 for f in fields_post:
3113 todo.setdefault(self._columns[f]._multi, [])
3114 todo[self._columns[f]._multi].append(f)
3115 for key, val in todo.items():
3117 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3120 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3121 multi_fields = res2.get(record['id'],{})
3123 record[pos] = multi_fields.get(pos,[])
3126 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3129 record[f] = res2[record['id']]
3134 for field in vals.copy():
3136 if field in self._columns:
3137 fobj = self._columns[field]
3144 for group in groups:
3145 module = group.split(".")[0]
3146 grp = group.split(".")[1]
3147 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" \
3148 (grp, module, 'res.groups', user))
3149 readonly = cr.fetchall()
3150 if readonly[0][0] >= 1:
3153 elif readonly[0][0] == 0:
3159 if type(vals[field]) == type([]):
3161 elif type(vals[field]) == type(0.0):
3163 elif type(vals[field]) == type(''):
3164 vals[field] = '=No Permission='
3169 def perm_read(self, cr, user, ids, context=None, details=True):
3171 Returns some metadata about the given records.
3173 :param details: if True, \*_uid fields are replaced with the name of the user
3174 :return: list of ownership dictionaries for each requested record
3175 :rtype: list of dictionaries with the following keys:
3178 * create_uid: user who created the record
3179 * create_date: date when the record was created
3180 * write_uid: last user who changed the record
3181 * write_date: date of the last change to the record
3182 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3189 uniq = isinstance(ids, (int, long))
3193 if self._log_access:
3194 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3195 quoted_table = '"%s"' % self._table
3196 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3197 query = '''SELECT %s, __imd.module, __imd.name
3198 FROM %s LEFT JOIN ir_model_data __imd
3199 ON (__imd.model = %%s and __imd.res_id = %s.id)
3200 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3201 cr.execute(query, (self._name, tuple(ids)))
3202 res = cr.dictfetchall()
3205 r[key] = r[key] or False
3206 if details and key in ('write_uid', 'create_uid'):
3208 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3209 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3210 del r['name'], r['module']
3215 def _check_concurrency(self, cr, ids, context):
3218 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3220 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3221 for sub_ids in cr.split_for_in_conditions(ids):
3224 id_ref = "%s,%s" % (self._name, id)
3225 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3227 ids_to_check.extend([id, update_date])
3228 if not ids_to_check:
3230 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3233 # mention the first one only to keep the error message readable
3234 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3236 def check_access_rule(self, cr, uid, ids, operation, context=None):
3237 """Verifies that the operation given by ``operation`` is allowed for the user
3238 according to ir.rules.
3240 :param operation: one of ``write``, ``unlink``
3241 :raise except_orm: * if current ir.rules do not permit this operation.
3242 :return: None if the operation is allowed
3244 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3246 where_clause = ' and ' + ' and '.join(where_clause)
3247 for sub_ids in cr.split_for_in_conditions(ids):
3248 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3249 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3250 [sub_ids] + where_params)
3251 if cr.rowcount != len(sub_ids):
3252 raise except_orm(_('AccessError'),
3253 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3254 % (operation, self._description))
3256 def unlink(self, cr, uid, ids, context=None):
3258 Delete records with given ids
3260 :param cr: database cursor
3261 :param uid: current user id
3262 :param ids: id or list of ids
3263 :param context: (optional) context arguments, like lang, time zone
3265 :raise AccessError: * if user has no unlink rights on the requested object
3266 * if user tries to bypass access rules for unlink on the requested object
3267 :raise UserError: if the record is default property for other records
3272 if isinstance(ids, (int, long)):
3275 result_store = self._store_get_values(cr, uid, ids, None, context)
3277 self._check_concurrency(cr, ids, context)
3279 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3281 properties = self.pool.get('ir.property')
3282 domain = [('res_id', '=', False),
3283 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3285 if properties.search(cr, uid, domain, context=context):
3286 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3288 wf_service = netsvc.LocalService("workflow")
3290 wf_service.trg_delete(uid, self._name, oid, cr)
3293 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3294 for sub_ids in cr.split_for_in_conditions(ids):
3295 cr.execute('delete from ' + self._table + ' ' \
3296 'where id IN %s', (sub_ids,))
3297 for order, object, store_ids, fields in result_store:
3298 if object != self._name:
3299 obj = self.pool.get(object)
3300 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3301 rids = map(lambda x: x[0], cr.fetchall())
3303 obj._store_set_values(cr, uid, rids, fields, context)
3309 def write(self, cr, user, ids, vals, context=None):
3311 Update records with given ids with the given field values
3313 :param cr: database cursor
3314 :param user: current user id
3316 :param ids: object id or list of object ids to update according to **vals**
3317 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3318 :type vals: dictionary
3319 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3320 :type context: dictionary
3322 :raise AccessError: * if user has no write rights on the requested object
3323 * if user tries to bypass access rules for write on the requested object
3324 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3325 :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)
3327 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3329 + For a many2many field, a list of tuples is expected.
3330 Here is the list of tuple that are accepted, with the corresponding semantics ::
3332 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3333 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3334 (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)
3335 (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)
3336 (4, ID) link to existing record with id = ID (adds a relationship)
3337 (5) unlink all (like using (3,ID) for all linked records)
3338 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3341 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3343 + For a one2many field, a lits of tuples is expected.
3344 Here is the list of tuple that are accepted, with the corresponding semantics ::
3346 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3347 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3348 (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)
3351 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3353 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3354 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3358 for field in vals.copy():
3360 if field in self._columns:
3361 fobj = self._columns[field]
3363 fobj = self._inherit_fields[field][2]
3370 for group in groups:
3371 module = group.split(".")[0]
3372 grp = group.split(".")[1]
3373 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", \
3374 (grp, module, 'res.groups', user))
3375 readonly = cr.fetchall()
3376 if readonly[0][0] >= 1:
3379 elif readonly[0][0] == 0:
3391 if isinstance(ids, (int, long)):
3394 self._check_concurrency(cr, ids, context)
3395 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3397 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3399 # No direct update of parent_left/right
3400 vals.pop('parent_left', None)
3401 vals.pop('parent_right', None)
3403 parents_changed = []
3404 if self._parent_store and (self._parent_name in vals):
3405 # The parent_left/right computation may take up to
3406 # 5 seconds. No need to recompute the values if the
3407 # parent is the same. Get the current value of the parent
3408 parent_val = vals[self._parent_name]
3410 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3411 (self._table, self._parent_name, self._parent_name)
3412 cr.execute(query, (tuple(ids), parent_val))
3414 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3415 (self._table, self._parent_name)
3416 cr.execute(query, (tuple(ids),))
3417 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3424 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3426 if field in self._columns:
3427 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3428 if (not totranslate) or not self._columns[field].translate:
3429 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3430 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3431 direct.append(field)
3433 upd_todo.append(field)
3435 updend.append(field)
3436 if field in self._columns \
3437 and hasattr(self._columns[field], 'selection') \
3439 if self._columns[field]._type == 'reference':
3440 val = vals[field].split(',')[0]
3443 if isinstance(self._columns[field].selection, (tuple, list)):
3444 if val not in dict(self._columns[field].selection):
3445 raise except_orm(_('ValidateError'),
3446 _('The value "%s" for the field "%s" is not in the selection') \
3447 % (vals[field], field))
3449 if val not in dict(self._columns[field].selection(
3450 self, cr, user, context=context)):
3451 raise except_orm(_('ValidateError'),
3452 _('The value "%s" for the field "%s" is not in the selection') \
3453 % (vals[field], field))
3455 if self._log_access:
3456 upd0.append('write_uid=%s')
3457 upd0.append('write_date=now()')
3461 self.check_access_rule(cr, user, ids, 'write', context=context)
3462 for sub_ids in cr.split_for_in_conditions(ids):
3463 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3464 'where id IN %s', upd1 + [sub_ids])
3465 if cr.rowcount != len(sub_ids):
3466 raise except_orm(_('AccessError'),
3467 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3472 if self._columns[f].translate:
3473 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3476 # Inserting value to DB
3477 self.write(cr, user, ids, {f: vals[f]})
3478 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3481 # call the 'set' method of fields which are not classic_write
3482 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3484 # default element in context must be removed when call a one2many or many2many
3485 rel_context = context.copy()
3486 for c in context.items():
3487 if c[0].startswith('default_'):
3488 del rel_context[c[0]]
3490 for field in upd_todo:
3492 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3494 for table in self._inherits:
3495 col = self._inherits[table]
3497 for sub_ids in cr.split_for_in_conditions(ids):
3498 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3499 'where id IN %s', (sub_ids,))
3500 nids.extend([x[0] for x in cr.fetchall()])
3504 if self._inherit_fields[val][0] == table:
3507 self.pool.get(table).write(cr, user, nids, v, context)
3509 self._validate(cr, user, ids, context)
3511 # TODO: use _order to set dest at the right position and not first node of parent
3512 # We can't defer parent_store computation because the stored function
3513 # fields that are computer may refer (directly or indirectly) to
3514 # parent_left/right (via a child_of domain)
3517 self.pool._init_parent[self._name] = True
3519 order = self._parent_order or self._order
3520 parent_val = vals[self._parent_name]
3522 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3524 clause, params = '%s IS NULL' % (self._parent_name,), ()
3525 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3526 parents = cr.fetchall()
3528 for id in parents_changed:
3529 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3530 pleft, pright = cr.fetchone()
3531 distance = pright - pleft + 1
3533 # Find Position of the element
3535 for (parent_pright, parent_id) in parents:
3538 position = parent_pright + 1
3540 # It's the first node of the parent
3545 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3546 position = cr.fetchone()[0] + 1
3548 if pleft < position <= pright:
3549 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3551 if pleft < position:
3552 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3553 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3554 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))
3556 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3557 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3558 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))
3560 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3564 for order, object, ids, fields in result:
3565 key = (object, tuple(fields))
3566 done.setdefault(key, {})
3567 # avoid to do several times the same computation
3570 if id not in done[key]:
3571 done[key][id] = True
3573 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3575 wf_service = netsvc.LocalService("workflow")
3577 wf_service.trg_write(user, self._name, id, cr)
3581 # TODO: Should set perm to user.xxx
3583 def create(self, cr, user, vals, context=None):
3585 Create new record with specified value
3587 :param cr: database cursor
3588 :param user: current user id
3590 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3591 :type vals: dictionary
3592 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3593 :type context: dictionary
3594 :return: id of new record created
3595 :raise AccessError: * if user has no create rights on the requested object
3596 * if user tries to bypass access rules for create on the requested object
3597 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3598 :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)
3600 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3601 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3607 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3609 vals = self._add_missing_default_values(cr, user, vals, context)
3612 for v in self._inherits:
3613 if self._inherits[v] not in vals:
3616 tocreate[v] = {'id': vals[self._inherits[v]]}
3617 (upd0, upd1, upd2) = ('', '', [])
3619 for v in vals.keys():
3620 if v in self._inherit_fields:
3621 (table, col, col_detail) = self._inherit_fields[v]
3622 tocreate[table][v] = vals[v]
3625 if (v not in self._inherit_fields) and (v not in self._columns):
3628 # Try-except added to filter the creation of those records whose filds are readonly.
3629 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3631 cr.execute("SELECT nextval('"+self._sequence+"')")
3633 raise except_orm(_('UserError'),
3634 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3636 id_new = cr.fetchone()[0]
3637 for table in tocreate:
3638 if self._inherits[table] in vals:
3639 del vals[self._inherits[table]]
3641 record_id = tocreate[table].pop('id', None)
3643 if record_id is None or not record_id:
3644 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3646 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3648 upd0 += ',' + self._inherits[table]
3650 upd2.append(record_id)
3652 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3653 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3655 for bool_field in bool_fields:
3656 if bool_field not in vals:
3657 vals[bool_field] = False
3659 for field in vals.copy():
3661 if field in self._columns:
3662 fobj = self._columns[field]
3664 fobj = self._inherit_fields[field][2]
3670 for group in groups:
3671 module = group.split(".")[0]
3672 grp = group.split(".")[1]
3673 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" % \
3674 (grp, module, 'res.groups', user))
3675 readonly = cr.fetchall()
3676 if readonly[0][0] >= 1:
3679 elif readonly[0][0] == 0:
3687 if self._columns[field]._classic_write:
3688 upd0 = upd0 + ',"' + field + '"'
3689 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3690 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3692 if not isinstance(self._columns[field], fields.related):
3693 upd_todo.append(field)
3694 if field in self._columns \
3695 and hasattr(self._columns[field], 'selection') \
3697 if self._columns[field]._type == 'reference':
3698 val = vals[field].split(',')[0]
3701 if isinstance(self._columns[field].selection, (tuple, list)):
3702 if val not in dict(self._columns[field].selection):
3703 raise except_orm(_('ValidateError'),
3704 _('The value "%s" for the field "%s" is not in the selection') \
3705 % (vals[field], field))
3707 if val not in dict(self._columns[field].selection(
3708 self, cr, user, context=context)):
3709 raise except_orm(_('ValidateError'),
3710 _('The value "%s" for the field "%s" is not in the selection') \
3711 % (vals[field], field))
3712 if self._log_access:
3713 upd0 += ',create_uid,create_date'
3716 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3717 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3718 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3720 if self._parent_store and not context.get('defer_parent_store_computation'):
3722 self.pool._init_parent[self._name] = True
3724 parent = vals.get(self._parent_name, False)
3726 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3728 result_p = cr.fetchall()
3729 for (pleft,) in result_p:
3734 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3735 pleft_old = cr.fetchone()[0]
3738 cr.execute('select max(parent_right) from '+self._table)
3739 pleft = cr.fetchone()[0] or 0
3740 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3741 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3742 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3744 # default element in context must be remove when call a one2many or many2many
3745 rel_context = context.copy()
3746 for c in context.items():
3747 if c[0].startswith('default_'):
3748 del rel_context[c[0]]
3751 for field in upd_todo:
3752 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3753 self._validate(cr, user, [id_new], context)
3755 if not context.get('no_store_function', False):
3756 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3759 for order, object, ids, fields2 in result:
3760 if not (object, ids, fields2) in done:
3761 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3762 done.append((object, ids, fields2))
3764 if self._log_create and not (context and context.get('no_store_function', False)):
3765 message = self._description + \
3767 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3768 "' " + _("created.")
3769 self.log(cr, user, id_new, message, True, context=context)
3770 wf_service = netsvc.LocalService("workflow")
3771 wf_service.trg_create(user, self._name, id_new, cr)
3774 def _store_get_values(self, cr, uid, ids, fields, context):
3776 fncts = self.pool._store_function.get(self._name, [])
3777 for fnct in range(len(fncts)):
3782 for f in (fields or []):
3783 if f in fncts[fnct][3]:
3789 result.setdefault(fncts[fnct][0], {})
3791 # uid == 1 for accessing objects having rules defined on store fields
3792 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3793 for id in filter(None, ids2):
3794 result[fncts[fnct][0]].setdefault(id, [])
3795 result[fncts[fnct][0]][id].append(fnct)
3797 for object in result:
3799 for id, fnct in result[object].items():
3800 k2.setdefault(tuple(fnct), [])
3801 k2[tuple(fnct)].append(id)
3802 for fnct, id in k2.items():
3803 dict.setdefault(fncts[fnct[0]][4], [])
3804 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3812 def _store_set_values(self, cr, uid, ids, fields, context):
3817 if self._log_access:
3818 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3822 field_dict.setdefault(r[0], [])
3823 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3824 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3825 for i in self.pool._store_function.get(self._name, []):
3827 up_write_date = write_date + datetime.timedelta(hours=i[5])
3828 if datetime.datetime.now() < up_write_date:
3830 field_dict[r[0]].append(i[1])
3836 if self._columns[f]._multi not in keys:
3837 keys.append(self._columns[f]._multi)
3838 todo.setdefault(self._columns[f]._multi, [])
3839 todo[self._columns[f]._multi].append(f)
3843 # uid == 1 for accessing objects having rules defined on store fields
3844 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3845 for id, value in result.items():
3847 for f in value.keys():
3848 if f in field_dict[id]:
3855 if self._columns[v]._type in ('many2one', 'one2one'):
3857 value[v] = value[v][0]
3860 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3861 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3864 cr.execute('update "' + self._table + '" set ' + \
3865 ','.join(upd0) + ' where id = %s', upd1)
3869 # uid == 1 for accessing objects having rules defined on store fields
3870 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3871 for r in result.keys():
3873 if r in field_dict.keys():
3874 if f in field_dict[r]:
3876 for id, value in result.items():
3877 if self._columns[f]._type in ('many2one', 'one2one'):
3882 cr.execute('update "' + self._table + '" set ' + \
3883 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3889 def perm_write(self, cr, user, ids, fields, context=None):
3890 raise NotImplementedError(_('This method does not exist anymore'))
3892 # TODO: ameliorer avec NULL
3893 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3894 """Computes the WHERE clause needed to implement an OpenERP domain.
3895 :param domain: the domain to compute
3897 :param active_test: whether the default filtering of records with ``active``
3898 field set to ``False`` should be applied.
3899 :return: the query expressing the given domain as provided in domain
3900 :rtype: osv.query.Query
3905 # if the object has a field named 'active', filter out all inactive
3906 # records unless they were explicitely asked for
3907 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3909 active_in_args = False
3911 if a[0] == 'active':
3912 active_in_args = True
3913 if not active_in_args:
3914 domain.insert(0, ('active', '=', 1))
3916 domain = [('active', '=', 1)]
3920 e = expression.expression(domain)
3921 e.parse(cr, user, self, context)
3922 tables = e.get_tables()
3923 where_clause, where_params = e.to_sql()
3924 where_clause = where_clause and [where_clause] or []
3926 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3928 return Query(tables, where_clause, where_params)
3930 def _check_qorder(self, word):
3931 if not regex_order.match(word):
3932 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)'))
3935 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3936 """Add what's missing in ``query`` to implement all appropriate ir.rules
3937 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3939 :param query: the current query object
3941 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3943 if parent_model and child_object:
3944 # as inherited rules are being applied, we need to add the missing JOIN
3945 # to reach the parent table (if it was not JOINed yet in the query)
3946 child_object._inherits_join_add(parent_model, query)
3947 query.where_clause += added_clause
3948 query.where_clause_params += added_params
3949 for table in added_tables:
3950 if table not in query.tables:
3951 query.tables.append(table)
3955 # apply main rules on the object
3956 rule_obj = self.pool.get('ir.rule')
3957 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3959 # apply ir.rules from the parents (through _inherits)
3960 for inherited_model in self._inherits:
3961 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3962 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3964 def _generate_m2o_order_by(self, order_field, query):
3966 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3967 either native m2o fields or function/related fields that are stored, including
3968 intermediate JOINs for inheritance if required.
3970 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3972 if order_field not in self._columns and order_field in self._inherit_fields:
3973 # also add missing joins for reaching the table containing the m2o field
3974 qualified_field = self._inherits_join_calc(order_field, query)
3975 order_field_column = self._inherit_fields[order_field][2]
3977 qualified_field = '"%s"."%s"' % (self._table, order_field)
3978 order_field_column = self._columns[order_field]
3980 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3981 assert order_field_column._classic_write or getattr(order_field_column, 'store', False), "Many2one function/related fields must be stored to be used as ordering fields"
3983 # figure out the applicable order_by for the m2o
3984 dest_model = self.pool.get(order_field_column._obj)
3985 m2o_order = dest_model._order
3986 if not regex_order.match(m2o_order):
3987 # _order is complex, can't use it here, so we default to _rec_name
3988 m2o_order = dest_model._rec_name
3990 # extract the first field name, to be able to qualify it and add desc/asc
3991 m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
3993 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3994 # as we don't want to exclude results that have NULL values for the m2o
3995 src_table, src_field = qualified_field.replace('"','').split('.', 1)
3996 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3997 return '"%s"."%s"' % (dest_model._table, m2o_order)
4000 def _generate_order_by(self, order_spec, query):
4002 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
4003 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
4005 :raise" except_orm in case order_spec is malformed
4007 order_by_clause = self._order
4009 order_by_elements = []
4010 self._check_qorder(order_spec)
4011 for order_part in order_spec.split(','):
4012 order_split = order_part.strip().split(' ')
4013 order_field = order_split[0].strip()
4014 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
4015 if order_field in self._columns:
4016 order_column = self._columns[order_field]
4017 if order_column._classic_read:
4018 order_by_clause = '"%s"."%s"' % (self._table, order_field)
4019 elif order_column._type == 'many2one':
4020 order_by_clause = self._generate_m2o_order_by(order_field, query)
4022 continue # ignore non-readable or "non-joignable" fields
4023 elif order_field in self._inherit_fields:
4024 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4025 order_column = parent_obj._columns[order_field]
4026 if order_column._classic_read:
4027 order_by_clause = self._inherits_join_calc(order_field, query)
4028 elif order_column._type == 'many2one':
4029 order_by_clause = self._generate_m2o_order_by(order_field, query)
4031 continue # ignore non-readable or "non-joignable" fields
4032 order_by_elements.append("%s %s" % (order_by_clause, order_direction))
4033 order_by_clause = ",".join(order_by_elements)
4035 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4037 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4039 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4040 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4041 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4042 This is ok at the security level because this method is private and not callable through XML-RPC.
4044 :param access_rights_uid: optional user ID to use when checking access rights
4045 (not for ir.rules, this is only for ir.model.access)
4049 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4051 query = self._where_calc(cr, user, args, context=context)
4052 self._apply_ir_rules(cr, user, query, 'read', context=context)
4053 order_by = self._generate_order_by(order, query)
4054 from_clause, where_clause, where_clause_params = query.get_sql()
4056 limit_str = limit and ' limit %d' % limit or ''
4057 offset_str = offset and ' offset %d' % offset or ''
4058 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4061 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4064 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4066 return [x[0] for x in res]
4068 # returns the different values ever entered for one field
4069 # this is used, for example, in the client when the user hits enter on
4071 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4074 if field in self._inherit_fields:
4075 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4077 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4079 def copy_data(self, cr, uid, id, default=None, context=None):
4081 Copy given record's data with all its fields values
4083 :param cr: database cursor
4084 :param user: current user id
4085 :param id: id of the record to copy
4086 :param default: field values to override in the original values of the copied record
4087 :type default: dictionary
4088 :param context: context arguments, like lang, time zone
4089 :type context: dictionary
4090 :return: dictionary containing all the field values
4097 if 'state' not in default:
4098 if 'state' in self._defaults:
4099 if callable(self._defaults['state']):
4100 default['state'] = self._defaults['state'](self, cr, uid, context)
4102 default['state'] = self._defaults['state']
4104 context_wo_lang = context
4105 if 'lang' in context:
4106 del context_wo_lang['lang']
4107 data = self.read(cr, uid, [id], context=context_wo_lang)[0]
4109 fields = self.fields_get(cr, uid, context=context)
4111 ftype = fields[f]['type']
4113 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4117 data[f] = default[f]
4118 elif ftype == 'function':
4120 elif ftype == 'many2one':
4122 data[f] = data[f] and data[f][0]
4125 elif ftype in ('one2many', 'one2one'):
4127 rel = self.pool.get(fields[f]['relation'])
4129 # duplicate following the order of the ids
4130 # because we'll rely on it later for copying
4131 # translations in copy_translation()!
4133 for rel_id in data[f]:
4134 # the lines are first duplicated using the wrong (old)
4135 # parent but then are reassigned to the correct one thanks
4136 # to the (0, 0, ...)
4137 d = rel.copy_data(cr, uid, rel_id, context=context)
4138 res.append((0, 0, d))
4140 elif ftype == 'many2many':
4141 data[f] = [(6, 0, data[f])]
4145 # make sure we don't break the current parent_store structure and
4146 # force a clean recompute!
4147 for parent_column in ['parent_left', 'parent_right']:
4148 data.pop(parent_column, None)
4150 for v in self._inherits:
4151 del data[self._inherits[v]]
4154 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4155 trans_obj = self.pool.get('ir.translation')
4156 fields = self.fields_get(cr, uid, context=context)
4158 translation_records = []
4159 for field_name, field_def in fields.items():
4160 # we must recursively copy the translations for o2o and o2m
4161 if field_def['type'] in ('one2one', 'one2many'):
4162 target_obj = self.pool.get(field_def['relation'])
4163 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4164 # here we rely on the order of the ids to match the translations
4165 # as foreseen in copy_data()
4166 old_children = sorted(old_record[field_name])
4167 new_children = sorted(new_record[field_name])
4168 for (old_child, new_child) in zip(old_children, new_children):
4169 # recursive copy of translations here
4170 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4171 # and for translatable fields we keep them for copy
4172 elif field_def.get('translate'):
4174 if field_name in self._columns:
4175 trans_name = self._name + "," + field_name
4176 elif field_name in self._inherit_fields:
4177 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4179 trans_ids = trans_obj.search(cr, uid, [
4180 ('name', '=', trans_name),
4181 ('res_id', '=', old_id)
4183 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4185 for record in translation_records:
4187 record['res_id'] = new_id
4188 trans_obj.create(cr, uid, record, context=context)
4191 def copy(self, cr, uid, id, default=None, context=None):
4193 Duplicate record with given id updating it with default values
4195 :param cr: database cursor
4196 :param uid: current user id
4197 :param id: id of the record to copy
4198 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4199 :type default: dictionary
4200 :param context: context arguments, like lang, time zone
4201 :type context: dictionary
4205 data = self.copy_data(cr, uid, id, default, context)
4206 new_id = self.create(cr, uid, data, context)
4207 self.copy_translations(cr, uid, id, new_id, context)
4210 def exists(self, cr, uid, ids, context=None):
4211 if type(ids) in (int, long):
4213 query = 'SELECT count(1) FROM "%s"' % (self._table)
4214 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4215 return cr.fetchone()[0] == len(ids)
4217 def check_recursion(self, cr, uid, ids, parent=None):
4219 Verifies that there is no loop in a hierarchical structure of records,
4220 by following the parent relationship using the **parent** field until a loop
4221 is detected or until a top-level record is found.
4223 :param cr: database cursor
4224 :param uid: current user id
4225 :param ids: list of ids of records to check
4226 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4227 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4231 parent = self._parent_name
4233 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4236 for i in range(0, len(ids), cr.IN_MAX):
4237 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4238 cr.execute(query, (tuple(sub_ids_parent),))
4239 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4240 ids_parent = ids_parent2
4241 for i in ids_parent:
4246 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4247 """Find out the XML ID of any database record, if there
4248 is one. This method works as a possible implementation
4249 for a function field, to be able to add it to any
4250 model object easily, referencing it as ``osv.osv.get_xml_id``.
4252 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4254 :return: the fully qualified XML ID of the given object,
4255 defaulting to an empty string when there's none
4256 (to be usable as a function field).
4258 result = dict.fromkeys(ids, '')
4259 model_data_obj = self.pool.get('ir.model.data')
4260 data_ids = model_data_obj.search(cr, uid,
4261 [('model', '=', self._name), ('res_id', 'in', ids)])
4262 data_results = model_data_obj.read(cr, uid, data_ids,
4263 ['name', 'module', 'res_id'])
4264 for record in data_results:
4265 result[record['res_id']] = '%(module)s.%(name)s' % record
4268 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: