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)
53 from lxml import etree
54 from tools.config import config
55 from tools.translate import _
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={}):
131 table : the object (inherited from orm)
132 context : dictionary with an optional context
136 self._list_class = list_class or browse_record_list
141 self._table_name = self._table._name
142 self.__logger = logging.getLogger(
143 'osv.browse_record.' + self._table_name)
144 self._context = context
145 self._fields_process = fields_process
147 cache.setdefault(table._name, {})
148 self._data = cache[table._name]
150 if not (id and isinstance(id, (int, long,))):
151 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
152 # if not table.exists(cr, uid, id, context):
153 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
155 if id not in self._data:
156 self._data[id] = {'id': id}
160 def __getitem__(self, name):
164 if name not in self._data[self._id]:
165 # build the list of fields we will fetch
167 # fetch the definition of the field which was asked for
168 if name in self._table._columns:
169 col = self._table._columns[name]
170 elif name in self._table._inherit_fields:
171 col = self._table._inherit_fields[name][2]
172 elif hasattr(self._table, str(name)):
173 attr = getattr(self._table, name)
175 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
176 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
180 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
181 "Field '%s' does not exist in object '%s': \n%s" % (
182 name, self, ''.join(traceback.format_exc())))
183 raise KeyError("Field '%s' does not exist in object '%s'" % (
186 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
188 # gen the list of "local" (ie not inherited) fields which are classic or many2one
189 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
190 # gen the list of inherited fields
191 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
192 # complete the field list with the inherited fields which are classic or many2one
193 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
194 # otherwise we fetch only that field
196 fields_to_fetch = [(name, col)]
197 ids = filter(lambda id: name not in self._data[id], self._data.keys())
199 field_names = map(lambda x: x[0], fields_to_fetch)
200 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
201 if self._fields_process:
202 lang = self._context.get('lang', 'en_US') or 'en_US'
203 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
205 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
206 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
208 for field_name, field_column in fields_to_fetch:
209 if field_column._type in self._fields_process:
210 for result_line in field_values:
211 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
212 if result_line[field_name]:
213 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
216 # Where did those ids come from? Perhaps old entries in ir_model_dat?
217 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
218 raise KeyError('Field %s not found in %s'%(name,self))
219 # create browse records for 'remote' objects
220 for result_line in field_values:
222 for field_name, field_column in fields_to_fetch:
223 if field_column._type in ('many2one', 'one2one'):
224 if result_line[field_name]:
225 obj = self._table.pool.get(field_column._obj)
226 if isinstance(result_line[field_name], (list,tuple)):
227 value = result_line[field_name][0]
229 value = result_line[field_name]
231 # FIXME: this happen when a _inherits object
232 # overwrite a field of it parent. Need
233 # testing to be sure we got the right
234 # object and not the parent one.
235 if not isinstance(value, browse_record):
236 new_data[field_name] = browse_record(self._cr,
237 self._uid, value, obj, self._cache,
238 context=self._context,
239 list_class=self._list_class,
240 fields_process=self._fields_process)
242 new_data[field_name] = value
244 new_data[field_name] = browse_null()
246 new_data[field_name] = browse_null()
247 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
248 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)
249 elif field_column._type in ('reference'):
250 if result_line[field_name]:
251 if isinstance(result_line[field_name], browse_record):
252 new_data[field_name] = result_line[field_name]
254 ref_obj, ref_id = result_line[field_name].split(',')
255 ref_id = long(ref_id)
256 obj = self._table.pool.get(ref_obj)
257 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)
259 new_data[field_name] = browse_null()
261 new_data[field_name] = result_line[field_name]
262 self._data[result_line['id']].update(new_data)
264 if not name in self._data[self._id]:
265 #how did this happen?
266 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
267 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
268 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
269 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
270 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
271 return self._data[self._id][name]
273 def __getattr__(self, name):
277 raise AttributeError(e)
279 def __contains__(self, name):
280 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
282 def __hasattr__(self, name):
289 return "browse_record(%s, %d)" % (self._table_name, self._id)
291 def __eq__(self, other):
292 if not isinstance(other, browse_record):
294 return (self._table_name, self._id) == (other._table_name, other._id)
296 def __ne__(self, other):
297 if not isinstance(other, browse_record):
299 return (self._table_name, self._id) != (other._table_name, other._id)
301 # we need to define __unicode__ even though we've already defined __str__
302 # because we have overridden __getattr__
303 def __unicode__(self):
304 return unicode(str(self))
307 return hash((self._table_name, self._id))
315 (type returned by postgres when the column was created, type expression to create the column)
319 fields.boolean: 'bool',
320 fields.integer: 'int4',
321 fields.integer_big: 'int8',
325 fields.datetime: 'timestamp',
326 fields.binary: 'bytea',
327 fields.many2one: 'int4',
329 if type(f) in type_dict:
330 f_type = (type_dict[type(f)], type_dict[type(f)])
331 elif isinstance(f, fields.float):
333 f_type = ('numeric', 'NUMERIC')
335 f_type = ('float8', 'DOUBLE PRECISION')
336 elif isinstance(f, (fields.char, fields.reference)):
337 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
338 elif isinstance(f, fields.selection):
339 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
340 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
341 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
344 f_size = getattr(f, 'size', None) or 16
347 f_type = ('int4', 'INTEGER')
349 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
350 elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict:
351 t = eval('fields.'+(f._type), globals())
352 f_type = (type_dict[t], type_dict[t])
353 elif isinstance(f, fields.function) and f._type == 'float':
355 f_type = ('numeric', 'NUMERIC')
357 f_type = ('float8', 'DOUBLE PRECISION')
358 elif isinstance(f, fields.function) and f._type == 'selection':
359 f_type = ('text', 'text')
360 elif isinstance(f, fields.function) and f._type == 'char':
361 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
363 logger = netsvc.Logger()
364 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
369 class orm_template(object):
375 _parent_name = 'parent_id'
376 _parent_store = False
377 _parent_order = False
387 CONCURRENCY_CHECK_FIELD = '__last_update'
388 def log(self, cr, uid, id, message, secondary=False, context=None):
389 return self.pool.get('res.log').create(cr, uid, {
391 'res_model': self._name,
392 'secondary': secondary,
397 def view_init(self, cr , uid , fields_list, context=None):
398 """Override this method to do specific things when a view on the object is opened."""
401 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
402 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
404 def _field_create(self, cr, context={}):
405 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
407 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
408 model_id = cr.fetchone()[0]
409 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'))
411 model_id = cr.fetchone()[0]
412 if 'module' in context:
413 name_id = 'model_'+self._name.replace('.','_')
414 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module']))
416 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
417 (name_id, context['module'], 'ir.model', model_id)
422 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
424 for rec in cr.dictfetchall():
425 cols[rec['name']] = rec
427 for (k, f) in self._columns.items():
429 'model_id': model_id,
432 'field_description': f.string.replace("'", " "),
434 'relation': f._obj or '',
435 'view_load': (f.view_load and 1) or 0,
436 'select_level': tools.ustr(f.select or 0),
437 'readonly':(f.readonly and 1) or 0,
438 'required':(f.required and 1) or 0,
439 'selectable' : (f.selectable and 1) or 0,
440 'relation_field': (f._type=='one2many' and isinstance(f,fields.one2many)) and f._fields_id or '',
442 # When its a custom field,it does not contain f.select
443 if context.get('field_state','base') == 'manual':
444 if context.get('field_name','') == k:
445 vals['select_level'] = context.get('select','0')
446 #setting value to let the problem NOT occur next time
448 vals['select_level'] = cols[k]['select_level']
451 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
452 id = cr.fetchone()[0]
454 cr.execute("""INSERT INTO ir_model_fields (
455 id, model_id, model, name, field_description, ttype,
456 relation,view_load,state,select_level,relation_field
458 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
460 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
461 vals['relation'], bool(vals['view_load']), 'base',
462 vals['select_level'],vals['relation_field']
464 if 'module' in context:
465 name1 = 'field_' + self._table + '_' + k
466 cr.execute("select name from ir_model_data where name=%s", (name1,))
468 name1 = name1 + "_" + str(id)
469 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
470 (name1, context['module'], 'ir.model.fields', id)
473 for key, val in vals.items():
474 if cols[k][key] != vals[key]:
475 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
477 cr.execute("""UPDATE ir_model_fields SET
478 model_id=%s, field_description=%s, ttype=%s, relation=%s,
479 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
481 model=%s AND name=%s""", (
482 vals['model_id'], vals['field_description'], vals['ttype'],
483 vals['relation'], bool(vals['view_load']),
484 vals['select_level'], bool(vals['readonly']),bool(vals['required']),bool(vals['selectable']),vals['relation_field'],vals['model'], vals['name']
489 def _auto_init(self, cr, context={}):
490 self._field_create(cr, context)
492 def __init__(self, cr):
493 if not self._name and not hasattr(self, '_inherit'):
494 name = type(self).__name__.split('.')[0]
495 msg = "The class %s has to have a _name attribute" % name
497 logger = netsvc.Logger()
498 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
499 raise except_orm('ValueError', msg )
501 if not self._description:
502 self._description = self._name
504 self._table = self._name.replace('.', '_')
506 def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
508 Fetch records as objects allowing to use dot notation to browse fields and relations
510 :param cr: database cursor
511 :param user: current user id
512 :param select: id or list of ids
513 :param context: context arguments, like lang, time zone
514 :rtype: object or list of objects requested
519 self._list_class = list_class or browse_record_list
521 # need to accepts ints and longs because ids coming from a method
522 # launched by button in the interface have a type long...
523 if isinstance(select, (int, long)):
524 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
525 elif isinstance(select, list):
526 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)
530 def __export_row(self, cr, uid, row, fields, context=None):
534 def check_type(field_type):
535 if field_type == 'float':
537 elif field_type == 'integer':
539 elif field_type == 'boolean':
543 def selection_field(in_field):
544 col_obj = self.pool.get(in_field.keys()[0])
545 if f[i] in col_obj._columns.keys():
546 return col_obj._columns[f[i]]
547 elif f[i] in col_obj._inherits.keys():
548 selection_field(col_obj._inherits)
554 data = map(lambda x: '', range(len(fields)))
556 for fpos in range(len(fields)):
565 model_data = self.pool.get('ir.model.data')
566 data_ids = model_data.search(cr, uid, [('model','=',r._table_name),('res_id','=',r['id'])])
568 d = model_data.read(cr, uid, data_ids, ['name','module'])[0]
570 r = '%s.%s'%(d['module'],d['name'])
577 # To display external name of selection field when its exported
578 if not context.get('import_comp',False):# Allow external name only if its not import compatible
580 if f[i] in self._columns.keys():
581 cols = self._columns[f[i]]
582 elif f[i] in self._inherit_fields.keys():
583 cols = selection_field(self._inherits)
584 if cols and cols._type == 'selection':
585 sel_list = cols.selection
586 if r and type(sel_list) == type([]):
587 r = [x[1] for x in sel_list if r==x[0]]
588 r = r and r[0] or False
590 if f[i] in self._columns:
591 r = check_type(self._columns[f[i]]._type)
592 elif f[i] in self._inherit_fields:
593 r = check_type(self._inherit_fields[f[i]][2]._type)
596 if isinstance(r, (browse_record_list, list)):
598 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
604 lines2 = self.__export_row(cr, uid, row2, fields2,
607 for fpos2 in range(len(fields)):
608 if lines2 and lines2[0][fpos2]:
609 data[fpos2] = lines2[0][fpos2]
613 if isinstance(rr.name, browse_record):
615 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
616 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
617 dt += tools.ustr(rr_name or '') + ','
627 if isinstance(r, browse_record):
628 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
629 r = r and r[0] and r[0][1] or ''
630 data[fpos] = tools.ustr(r or '')
631 return [data] + lines
633 def export_data(self, cr, uid, ids, fields_to_export, context=None):
635 Export fields for selected objects
637 :param cr: database cursor
638 :param uid: current user id
639 :param ids: list of ids
640 :param fields_to_export: list of fields
641 :param context: context arguments, like lang, time zone, may contain import_comp(default: False) to make exported data compatible with import_data()
642 :rtype: dictionary with a *datas* matrix
644 This method is used when exporting data via client menu
649 imp_comp = context.get('import_comp',False)
650 cols = self._columns.copy()
651 for f in self._inherit_fields:
652 cols.update({f: self._inherit_fields[f][2]})
653 fields_to_export = map(lambda x: x.split('/'), fields_to_export)
654 fields_export = fields_to_export+[]
657 for field in fields_export:
658 if imp_comp and len(field)>1:
659 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
660 elif len (field) <=1:
661 if imp_comp and cols.get(field and field[0],False):
662 if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
663 or isinstance(cols[field[0]], fields.related)\
664 or isinstance(cols[field[0]], fields.one2many)):
665 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
667 if imp_comp and len(warning_fields):
668 warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' %('\n'.join(warning_fields))
670 return {'warning' : warning}
671 for row in self.browse(cr, uid, ids, context):
672 datas += self.__export_row(cr, uid, row, fields_to_export, context)
673 return {'datas':datas}
675 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
677 Import given data in given module
679 :param cr: database cursor
680 :param uid: current user id
681 :param ids: list of ids
682 :param fields: list of fields
683 :param data: data to import
684 :param mode: 'init' or 'update' for record creation
685 :param current_module: module name
686 :param noupdate: flag for record creation
687 :param context: context arguments, like lang, time zone,
688 :param filename: optional file to store partial import state for recovery
691 This method is used when importing data via client menu
696 fields = map(lambda x: x.split('/'), fields)
697 logger = netsvc.Logger()
698 ir_model_data_obj = self.pool.get('ir.model.data')
700 def _check_db_id(self, model_name, db_id):
701 obj_model = self.pool.get(model_name)
702 ids = obj_model.search(cr, uid, [('id','=',int(db_id))])
704 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
707 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
708 line = datas[position]
717 ir_model_data_obj = self.pool.get('ir.model.data')
719 # Import normal fields
721 for i in range(len(fields)):
723 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
728 if prefix and not prefix[0] in field:
731 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
735 field_name = field[0].split(':')[0]
736 model_rel = fields_def[field_name]['relation']
738 if fields_def[field[len(prefix)][:-6]]['type']=='many2many':
740 for db_id in line[i].split(config.get('csv_internal_sep')):
742 _check_db_id(self, model_rel, db_id)
745 warning += [tools.exception_to_unicode(e)]
746 logger.notifyChannel("import", netsvc.LOG_ERROR,
747 tools.exception_to_unicode(e))
749 res = [(6, 0, res_id)]
752 _check_db_id(self, model_rel, line[i])
755 warning += [tools.exception_to_unicode(e)]
756 logger.notifyChannel("import", netsvc.LOG_ERROR,
757 tools.exception_to_unicode(e))
758 row[field_name] = res or False
761 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
764 if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
766 for word in line[i].split(config.get('csv_internal_sep')):
768 module, xml_id = word.rsplit('.', 1)
770 module, xml_id = current_module, word
771 id = ir_model_data_obj._get_id(cr, uid, module,
773 res_id2 = ir_model_data_obj.read(cr, uid, [id],
774 ['res_id'])[0]['res_id']
776 res_id.append(res_id2)
778 res_id = [(6, 0, res_id)]
781 module, xml_id = line[i].rsplit('.', 1)
783 module, xml_id = current_module, line[i]
784 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
785 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
787 res_id = ir_model_data[0]['res_id']
789 raise ValueError('No references to %s.%s' % (module, xml_id))
790 row[field[-1][:-3]] = res_id or False
792 if (len(field) == len(prefix)+1) and \
793 len(field[len(prefix)].split(':lang=')) == 2:
794 f, lang = field[len(prefix)].split(':lang=')
795 translate.setdefault(lang, {})[f]=line[i] or False
797 if (len(field) == len(prefix)+1) and \
798 (prefix == field[0:len(prefix)]):
799 if field[len(prefix)] == "id":
802 is_xml_id = data_id = line[i]
803 d = data_id.split('.')
804 module = len(d)>1 and d[0] or ''
805 name = len(d)>1 and d[1] or d[0]
806 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('name','=',name)])
808 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
810 if is_db_id and not db_id:
811 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('res_id','=',is_db_id)])
812 if not len(data_ids):
813 ir_model_data_obj.create(cr, uid, {'module':module, 'model':model_name, 'name':name, 'res_id':is_db_id})
815 if is_db_id and int(db_id) != int(is_db_id):
816 warning += [_("Id is not the same than existing one: %s")%(is_db_id)]
817 logger.notifyChannel("import", netsvc.LOG_ERROR,
818 _("Id is not the same than existing one: %s")%(is_db_id))
821 if field[len(prefix)] == "db_id":
824 _check_db_id(self, model_name, line[i])
825 data_res_id = is_db_id = int(line[i])
827 warning += [tools.exception_to_unicode(e)]
828 logger.notifyChannel("import", netsvc.LOG_ERROR,
829 tools.exception_to_unicode(e))
831 data_ids = ir_model_data_obj.search(cr, uid, [('model','=',model_name),('res_id','=',line[i])])
833 d = ir_model_data_obj.read(cr, uid, data_ids, ['name','module'])[0]
836 data_id = '%s.%s'%(d['module'],d['name'])
839 if is_xml_id and not data_id:
841 if is_xml_id and is_xml_id!=data_id:
842 warning += [_("Id is not the same than existing one: %s")%(line[i])]
843 logger.notifyChannel("import", netsvc.LOG_ERROR,
844 _("Id is not the same than existing one: %s")%(line[i]))
847 if fields_def[field[len(prefix)]]['type'] == 'integer':
848 res = line[i] and int(line[i])
849 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
850 res = line[i].lower() not in ('0', 'false', 'off')
851 elif fields_def[field[len(prefix)]]['type'] == 'float':
852 res = line[i] and float(line[i])
853 elif fields_def[field[len(prefix)]]['type'] == 'selection':
855 if isinstance(fields_def[field[len(prefix)]]['selection'],
857 sel = fields_def[field[len(prefix)]]['selection']
859 sel = fields_def[field[len(prefix)]]['selection'](self,
862 if line[i] in [tools.ustr(key),tools.ustr(val)]: #Acepting key or value for selection field
865 if line[i] and not res:
866 logger.notifyChannel("import", netsvc.LOG_WARNING,
867 _("key '%s' not found in selection field '%s'") % \
868 (line[i], field[len(prefix)]))
870 warning += [_("Key/value '%s' not found in selection field '%s'")%(line[i],field[len(prefix)])]
872 elif fields_def[field[len(prefix)]]['type']=='many2one':
875 relation = fields_def[field[len(prefix)]]['relation']
876 res2 = self.pool.get(relation).name_search(cr, uid,
877 line[i], [], operator='=', context=context)
878 res = (res2 and res2[0][0]) or False
880 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
881 logger.notifyChannel("import", netsvc.LOG_WARNING,
882 _("Relation not found: %s on '%s'")%(line[i],relation))
883 elif fields_def[field[len(prefix)]]['type']=='many2many':
886 relation = fields_def[field[len(prefix)]]['relation']
887 for word in line[i].split(config.get('csv_internal_sep')):
888 res2 = self.pool.get(relation).name_search(cr,
889 uid, word, [], operator='=', context=context)
890 res3 = (res2 and res2[0][0]) or False
892 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
893 logger.notifyChannel("import",
895 _("Relation not found: %s on '%s'")%(line[i],relation))
901 res = line[i] or False
902 row[field[len(prefix)]] = res
903 elif (prefix==field[0:len(prefix)]):
904 if field[0] not in todo:
905 todo.append(field[len(prefix)])
907 # Import one2many, many2many fields
911 relation_obj = self.pool.get(fields_def[field]['relation'])
912 newfd = relation_obj.fields_get(
913 cr, uid, context=context)
914 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
915 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
916 nbrmax = max(nbrmax, max2)
917 warning = warning + w2
918 reduce(lambda x, y: x and y, newrow)
919 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
920 [(0, 0, newrow)]) or []
922 while (position+i)<len(datas):
924 for j in range(len(fields)):
926 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
931 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
932 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
934 if reduce(lambda x, y: x or y, newrow.values()):
935 row[field].append((0, 0, newrow))
937 nbrmax = max(nbrmax, i)
940 for i in range(max(nbrmax, 1)):
943 result = (row, nbrmax, warning, translate, data_id, data_res_id)
946 fields_def = self.fields_get(cr, uid, context=context)
949 initial_size = len(datas)
950 if config.get('import_partial', False) and filename:
951 data = pickle.load(file(config.get('import_partial')))
952 original_value = data.get(filename, 0)
958 (res, other, warning, translate, data_id, res_id) = \
959 process_liness(self, datas, [], current_module, self._name, fields_def)
962 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
965 id = ir_model_data_obj._update(cr, uid, self._name,
966 current_module, res, xml_id=data_id, mode=mode,
967 noupdate=noupdate, res_id=res_id, context=context)
972 if isinstance(e,psycopg2.IntegrityError):
973 msg= _('Insertion Failed! ')
974 for key in self.pool._sql_error.keys():
976 msg = self.pool._sql_error[key]
978 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
979 if isinstance(e, osv.orm.except_orm ):
980 msg = _('Insertion Failed! ' + e[1])
981 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
982 #Raising Uncaught exception
983 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '' )
985 for lang in translate:
986 context2 = context.copy()
987 context2['lang'] = lang
988 self.write(cr, uid, [id], translate[lang], context2)
989 if config.get('import_partial', False) and filename and (not (counter%100)) :
990 data = pickle.load(file(config.get('import_partial')))
991 data[filename] = initial_size - len(datas) + original_value
992 pickle.dump(data, file(config.get('import_partial'),'wb'))
993 if context.get('defer_parent_store_computation'):
994 self._parent_store_compute(cr)
997 #except Exception, e:
998 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1001 # return (-1, res, e[0], warning)
1003 # return (-1, res, e[0], '')
1006 # TODO: Send a request with the result and multi-thread !
1008 if context.get('defer_parent_store_computation'):
1009 self._parent_store_compute(cr)
1010 return (done, 0, 0, 0)
1012 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1013 raise NotImplementedError(_('The read method is not implemented on this object !'))
1015 def get_invalid_fields(self,cr,uid):
1016 return list(self._invalids)
1018 def _validate(self, cr, uid, ids, context=None):
1019 context = context or {}
1020 lng = context.get('lang', False) or 'en_US'
1021 trans = self.pool.get('ir.translation')
1023 for constraint in self._constraints:
1024 fun, msg, fields = constraint
1025 if not fun(self, cr, uid, ids):
1026 # Check presence of __call__ directly instead of using
1027 # callable() because it will be deprecated as of Python 3.0
1028 if hasattr(msg, '__call__'):
1029 txt_msg, params = msg(self, cr, uid, ids)
1030 tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg
1031 translated_msg = tmp_msg % params
1033 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1035 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1037 self._invalids.update(fields)
1040 raise except_orm('ValidateError', '\n'.join(error_msgs))
1042 self._invalids.clear()
1044 def default_get(self, cr, uid, fields_list, context=None):
1046 Returns default values for the fields in fields_list.
1048 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1049 :type fields_list: list
1050 :param context: usual context dictionary - it may contains keys in the form ``default_XXX``,
1051 where XXX is a field name to set or override a default value.
1052 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1054 # trigger view init hook
1055 self.view_init(cr, uid, fields_list, context)
1061 # get the default values for the inherited fields
1062 for t in self._inherits.keys():
1063 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1066 # get the default values defined in the object
1067 for f in fields_list:
1068 if f in self._defaults:
1069 if callable(self._defaults[f]):
1070 defaults[f] = self._defaults[f](self, cr, uid, context)
1072 defaults[f] = self._defaults[f]
1074 fld_def = ((f in self._columns) and self._columns[f]) \
1075 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1078 if isinstance(fld_def, fields.property):
1079 property_obj = self.pool.get('ir.property')
1080 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1082 if isinstance(prop_value, (browse_record, browse_null)):
1083 defaults[f] = prop_value.id
1085 defaults[f] = prop_value
1087 if f not in defaults:
1090 # get the default values set by the user and override the default
1091 # values defined in the object
1092 ir_values_obj = self.pool.get('ir.values')
1093 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1094 for id, field, field_value in res:
1095 if field in fields_list:
1096 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1097 if fld_def._type in ('many2one', 'one2one'):
1098 obj = self.pool.get(fld_def._obj)
1099 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1101 if fld_def._type in ('many2many'):
1102 obj = self.pool.get(fld_def._obj)
1104 for i in range(len(field_value)):
1105 if not obj.search(cr, uid, [('id', '=',
1108 field_value2.append(field_value[i])
1109 field_value = field_value2
1110 if fld_def._type in ('one2many'):
1111 obj = self.pool.get(fld_def._obj)
1113 for i in range(len(field_value)):
1114 field_value2.append({})
1115 for field2 in field_value[i]:
1116 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1117 obj2 = self.pool.get(obj._columns[field2]._obj)
1118 if not obj2.search(cr, uid,
1119 [('id', '=', field_value[i][field2])]):
1121 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1122 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1123 if not obj2.search(cr, uid,
1124 [('id', '=', field_value[i][field2])]):
1126 # TODO add test for many2many and one2many
1127 field_value2[i][field2] = field_value[i][field2]
1128 field_value = field_value2
1129 defaults[field] = field_value
1131 # get the default values from the context
1132 for key in context or {}:
1133 if key.startswith('default_') and (key[8:] in fields_list):
1134 defaults[key[8:]] = context[key]
1138 def perm_read(self, cr, user, ids, context=None, details=True):
1139 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1141 def unlink(self, cr, uid, ids, context=None):
1142 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1144 def write(self, cr, user, ids, vals, context=None):
1145 raise NotImplementedError(_('The write method is not implemented on this object !'))
1147 def create(self, cr, user, vals, context=None):
1148 raise NotImplementedError(_('The create method is not implemented on this object !'))
1150 def fields_get_keys(self, cr, user, context=None):
1151 res = self._columns.keys()
1152 for parent in self._inherits:
1153 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1156 # returns the definition of each field in the object
1157 # the optional fields parameter can limit the result to some fields
1158 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1162 translation_obj = self.pool.get('ir.translation')
1163 for parent in self._inherits:
1164 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1166 if self._columns.keys():
1167 for f in self._columns.keys():
1168 if allfields and f not in allfields:
1170 res[f] = {'type': self._columns[f]._type}
1171 # This additional attributes for M2M and function field is added
1172 # because we need to display tooltip with this additional information
1173 # when client is started in debug mode.
1174 if isinstance(self._columns[f], fields.function):
1175 res[f]['function'] = self._columns[f]._fnct and self._columns[f]._fnct.func_name or False
1176 res[f]['store'] = self._columns[f].store
1177 if isinstance(self._columns[f].store, dict):
1178 res[f]['store'] = str(self._columns[f].store)
1179 res[f]['fnct_search'] = self._columns[f]._fnct_search and self._columns[f]._fnct_search.func_name or False
1180 res[f]['fnct_inv'] = self._columns[f]._fnct_inv and self._columns[f]._fnct_inv.func_name or False
1181 res[f]['fnct_inv_arg'] = self._columns[f]._fnct_inv_arg or False
1182 res[f]['func_obj'] = self._columns[f]._obj or False
1183 res[f]['func_method'] = self._columns[f]._method
1184 if isinstance(self._columns[f], fields.many2many):
1185 res[f]['related_columns'] = list((self._columns[f]._id1, self._columns[f]._id2))
1186 res[f]['third_table'] = self._columns[f]._rel
1187 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1188 'change_default', 'translate', 'help', 'select', 'selectable'):
1189 if getattr(self._columns[f], arg):
1190 res[f][arg] = getattr(self._columns[f], arg)
1191 if not write_access:
1192 res[f]['readonly'] = True
1193 res[f]['states'] = {}
1194 for arg in ('digits', 'invisible','filters'):
1195 if getattr(self._columns[f], arg, None):
1196 res[f][arg] = getattr(self._columns[f], arg)
1198 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', self._columns[f].string)
1200 res[f]['string'] = res_trans
1201 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1203 res[f]['help'] = help_trans
1205 if hasattr(self._columns[f], 'selection'):
1206 if isinstance(self._columns[f].selection, (tuple, list)):
1207 sel = self._columns[f].selection
1208 # translate each selection option
1210 for (key, val) in sel:
1213 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1214 sel2.append((key, val2 or val))
1216 res[f]['selection'] = sel
1218 # call the 'dynamic selection' function
1219 res[f]['selection'] = self._columns[f].selection(self, cr,
1221 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1222 res[f]['relation'] = self._columns[f]._obj
1223 res[f]['domain'] = self._columns[f]._domain
1224 res[f]['context'] = self._columns[f]._context
1226 #TODO : read the fields from the database
1230 # filter out fields which aren't in the fields list
1231 for r in res.keys():
1232 if r not in allfields:
1237 # Overload this method if you need a window title which depends on the context
1239 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1242 def __view_look_dom(self, cr, user, node, view_id, context=None):
1250 if isinstance(s, unicode):
1251 return s.encode('utf8')
1254 # return True if node can be displayed to current user
1255 def check_group(node):
1256 if node.get('groups'):
1257 groups = node.get('groups').split(',')
1258 access_pool = self.pool.get('ir.model.access')
1259 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1261 node.set('invisible', '1')
1262 if 'attrs' in node.attrib:
1263 del(node.attrib['attrs']) #avoid making field visible later
1264 del(node.attrib['groups'])
1269 if node.tag in ('field', 'node', 'arrow'):
1270 if node.get('object'):
1275 if f.tag in ('field'):
1276 xml += etree.tostring(f, encoding="utf-8")
1278 new_xml = etree.fromstring(encode(xml))
1279 ctx = context.copy()
1280 ctx['base_model_name'] = self._name
1281 xarch, xfields = self.pool.get(node.get('object',False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1282 views[str(f.tag)] = {
1286 attrs = {'views': views}
1288 fields = views.get('field',False) and views['field'].get('fields',False)
1289 if node.get('name'):
1292 if node.get('name') in self._columns:
1293 column = self._columns[node.get('name')]
1295 column = self._inherit_fields[node.get('name')][2]
1300 relation = self.pool.get(column._obj)
1305 if f.tag in ('form', 'tree', 'graph'):
1307 ctx = context.copy()
1308 ctx['base_model_name'] = self._name
1309 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1310 views[str(f.tag)] = {
1314 attrs = {'views': views}
1315 if node.get('widget') and node.get('widget') == 'selection':
1316 # Prepare the cached selection list for the client. This needs to be
1317 # done even when the field is invisible to the current user, because
1318 # other events could need to change its value to any of the selectable ones
1319 # (such as on_change events, refreshes, etc.)
1321 # If domain and context are strings, we keep them for client-side, otherwise
1322 # we evaluate them server-side to consider them when generating the list of
1324 # TODO: find a way to remove this hack, by allow dynamic domains
1326 if column._domain and not isinstance(column._domain, basestring):
1327 dom = column._domain
1328 dom += eval(node.get('domain','[]'), {'uid':user, 'time':time})
1329 search_context = dict(context)
1330 if column._context and not isinstance(column._context, basestring):
1331 search_context.update(column._context)
1332 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1333 if (node.get('required') and not int(node.get('required'))) or not column.required:
1334 attrs['selection'].append((False,''))
1335 fields[node.get('name')] = attrs
1337 elif node.tag in ('form', 'tree'):
1338 result = self.view_header_get(cr, user, False, node.tag, context)
1340 node.set('string', result)
1342 elif node.tag == 'calendar':
1343 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1344 if node.get(additional_field):
1345 fields[node.get(additional_field)] = {}
1347 if 'groups' in node.attrib:
1351 if ('lang' in context) and not result:
1352 if node.get('string'):
1353 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string').encode('utf8'))
1354 if not trans and ('base_model_name' in context):
1355 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string').encode('utf8'))
1357 node.set('string', trans)
1359 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum').encode('utf8'))
1361 node.set('sum', trans)
1365 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1369 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1370 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1372 rolesobj = self.pool.get('res.roles')
1373 usersobj = self.pool.get('res.users')
1375 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1376 for button in buttons:
1378 if user != 1: # admin user has all roles
1379 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
1380 # TODO handle the case of more than one workflow for a model
1381 cr.execute("""SELECT DISTINCT t.role_id
1383 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1384 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1387 """, (self._name, button.get('name'),))
1388 roles = cr.fetchall()
1390 # draft -> valid = signal_next (role X)
1391 # draft -> cancel = signal_cancel (no role)
1393 # valid -> running = signal_next (role Y)
1394 # valid -> cancel = signal_cancel (role Z)
1396 # running -> done = signal_next (role Z)
1397 # running -> cancel = signal_cancel (role Z)
1399 # As we don't know the object state, in this scenario,
1400 # the button "signal_cancel" will be always shown as there is no restriction to cancel in draft
1401 # the button "signal_next" will be show if the user has any of the roles (X Y or Z)
1402 # The verification will be made later in workflow process...
1404 can_click = any((not role) or rolesobj.check(cr, user, user_roles, role) for (role,) in roles)
1406 button.set('readonly', str(int(not can_click)))
1408 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1411 if node.tag=='diagram':
1412 if node.getchildren()[0].tag=='node':
1413 node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1414 if node.getchildren()[1].tag=='arrow':
1415 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1416 for key,value in node_fields.items():
1418 for key,value in arrow_fields.items():
1421 fields = self.fields_get(cr, user, fields_def.keys(), context)
1422 for field in fields_def:
1424 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1425 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1426 elif field in fields:
1427 fields[field].update(fields_def[field])
1429 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))
1430 res = cr.fetchall()[:]
1432 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1433 msg = "\n * ".join([r[0] for r in res])
1434 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1435 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1436 raise except_orm('View error', msg)
1439 def __get_default_calendar_view(self):
1440 """Generate a default calendar view (For internal use only).
1443 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1444 '<calendar string="%s"') % (self._description)
1446 if (self._date_name not in self._columns):
1448 for dt in ['date','date_start','x_date','x_date_start']:
1449 if dt in self._columns:
1450 self._date_name = dt
1455 raise except_orm(_('Invalid Object Architecture!'),_("Insufficient fields for Calendar View!"))
1458 arch +=' date_start="%s"' % (self._date_name)
1460 for color in ["user_id","partner_id","x_user_id","x_partner_id"]:
1461 if color in self._columns:
1462 arch += ' color="' + color + '"'
1465 dt_stop_flag = False
1467 for dt_stop in ["date_stop","date_end","x_date_stop","x_date_end"]:
1468 if dt_stop in self._columns:
1469 arch += ' date_stop="' + dt_stop + '"'
1473 if not dt_stop_flag:
1474 for dt_delay in ["date_delay","planned_hours","x_date_delay","x_planned_hours"]:
1475 if dt_delay in self._columns:
1476 arch += ' date_delay="' + dt_delay + '"'
1480 ' <field name="%s"/>\n'
1481 '</calendar>') % (self._rec_name)
1485 def __get_default_search_view(self, cr, uid, context={}):
1488 if isinstance(s, unicode):
1489 return s.encode('utf8')
1492 view = self.fields_view_get(cr, uid, False, 'form', context)
1494 root = etree.fromstring(encode(view['arch']))
1495 res = etree.XML("""<search string="%s"></search>""" % root.get("string", ""))
1496 node = etree.Element("group")
1499 fields = root.xpath("//field[@select=1]")
1500 for field in fields:
1503 return etree.tostring(res, encoding="utf-8").replace('\t', '')
1506 # if view_id, view_type is not required
1508 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1510 Get the detailed composition of the requested view like fields, model, view architecture
1512 :param cr: database cursor
1513 :param user: current user id
1514 :param view_id: id of the view or None
1515 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1516 :param context: context arguments, like lang, time zone
1517 :param toolbar: true to include contextual actions
1518 :param submenu: example (portal_project module)
1519 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1520 :raise AttributeError:
1521 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1522 * if some tag other than 'position' is found in parent view
1523 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1530 if isinstance(s, unicode):
1531 return s.encode('utf8')
1534 def _inherit_apply(src, inherit):
1535 def _find(node, node2):
1536 if node2.tag == 'xpath':
1537 res = node.xpath(node2.get('expr'))
1543 for n in node.getiterator(node2.tag):
1545 for attr in node2.attrib:
1546 if attr == 'position':
1549 if n.get(attr) == node2.get(attr):
1556 # End: _find(node, node2)
1558 doc_dest = etree.fromstring(encode(inherit))
1559 toparse = [ doc_dest ]
1562 node2 = toparse.pop(0)
1563 if node2.tag == 'data':
1564 toparse += [ c for c in doc_dest ]
1566 node = _find(src, node2)
1567 if node is not None:
1569 if node2.get('position'):
1570 pos = node2.get('position')
1571 if pos == 'replace':
1572 parent = node.getparent()
1574 src = copy.deepcopy(node2[0])
1577 node.addprevious(child)
1578 node.getparent().remove(node)
1579 elif pos == 'attributes':
1580 for child in node2.getiterator('attribute'):
1581 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1583 node.set(attribute[0], attribute[1])
1585 del(node.attrib[attribute[0]])
1587 sib = node.getnext()
1591 elif pos == 'after':
1595 sib.addprevious(child)
1596 elif pos == 'before':
1597 node.addprevious(child)
1599 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1602 ' %s="%s"' % (attr, node2.get(attr))
1603 for attr in node2.attrib
1604 if attr != 'position'
1606 tag = "<%s%s>" % (node2.tag, attrs)
1607 raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1609 # End: _inherit_apply(src, inherit)
1611 result = {'type': view_type, 'model': self._name}
1617 view_ref = context.get(view_type + '_view_ref', False)
1618 if view_ref and not view_id:
1620 module, view_ref = view_ref.split('.', 1)
1621 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1622 view_ref_res = cr.fetchone()
1624 view_id = view_ref_res[0]
1627 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1630 query += " AND model=%s"
1631 params += (self._name,)
1632 cr.execute(query, params)
1634 cr.execute('''SELECT
1635 arch,name,field_parent,id,type,inherit_id
1642 ORDER BY priority''', (self._name, view_type))
1643 sql_res = cr.fetchone()
1649 view_id = ok or sql_res[3]
1652 # if a view was found
1654 result['type'] = sql_res[4]
1655 result['view_id'] = sql_res[3]
1656 result['arch'] = sql_res[0]
1658 def _inherit_apply_rec(result, inherit_id):
1659 # get all views which inherit from (ie modify) this view
1660 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1661 sql_inherit = cr.fetchall()
1662 for (inherit, id) in sql_inherit:
1663 result = _inherit_apply(result, inherit)
1664 result = _inherit_apply_rec(result, id)
1667 inherit_result = etree.fromstring(encode(result['arch']))
1668 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1670 result['name'] = sql_res[1]
1671 result['field_parent'] = sql_res[2] or False
1674 # otherwise, build some kind of default view
1675 if view_type == 'form':
1676 res = self.fields_get(cr, user, context=context)
1677 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1678 '<form string="%s">' % (self._description,)
1680 if res[x]['type'] not in ('one2many', 'many2many'):
1681 xml += '<field name="%s"/>' % (x,)
1682 if res[x]['type'] == 'text':
1686 elif view_type == 'tree':
1687 _rec_name = self._rec_name
1688 if _rec_name not in self._columns:
1689 _rec_name = self._columns.keys()[0]
1690 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1691 '<tree string="%s"><field name="%s"/></tree>' \
1692 % (self._description, self._rec_name)
1694 elif view_type == 'calendar':
1695 xml = self.__get_default_calendar_view()
1697 elif view_type == 'search':
1698 xml = self.__get_default_search_view(cr, user, context)
1701 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1702 raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type)
1703 result['arch'] = etree.fromstring(encode(xml))
1704 result['name'] = 'default'
1705 result['field_parent'] = False
1706 result['view_id'] = 0
1708 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1709 result['arch'] = xarch
1710 result['fields'] = xfields
1713 if context and context.get('active_id',False):
1714 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1716 act_id = data_menu.id
1718 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1719 result['submenu'] = getattr(data_action,'menus', False)
1723 for key in ('report_sxw_content', 'report_rml_content',
1724 'report_sxw', 'report_rml',
1725 'report_sxw_content_data', 'report_rml_content_data'):
1729 ir_values_obj = self.pool.get('ir.values')
1730 resprint = ir_values_obj.get(cr, user, 'action',
1731 'client_print_multi', [(self._name, False)], False,
1733 resaction = ir_values_obj.get(cr, user, 'action',
1734 'client_action_multi', [(self._name, False)], False,
1737 resrelate = ir_values_obj.get(cr, user, 'action',
1738 'client_action_relate', [(self._name, False)], False,
1740 resprint = map(clean, resprint)
1741 resaction = map(clean, resaction)
1742 resaction = filter(lambda x: not x.get('multi', False), resaction)
1743 resprint = filter(lambda x: not x.get('multi', False), resprint)
1744 resrelate = map(lambda x: x[2], resrelate)
1746 for x in resprint+resaction+resrelate:
1747 x['string'] = x['name']
1749 result['toolbar'] = {
1751 'action': resaction,
1754 if result['type']=='form' and result['arch'].count("default_focus")>1:
1755 msg = "Form View contain more than one default_focus attribute"
1756 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1757 raise except_orm('View Error !',msg)
1760 _view_look_dom_arch = __view_look_dom_arch
1762 def search_count(self, cr, user, args, context=None):
1765 res = self.search(cr, user, args, context=context, count=True)
1766 if isinstance(res, list):
1770 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1772 Search for records based on a search domain.
1774 :param cr: database cursor
1775 :param user: current user id
1776 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1777 :param offset: optional number of results to skip in the returned values (default: 0)
1778 :param limit: optional max number of records to return (default: **None**)
1779 :param order: optional columns to sort by (default: self._order=id )
1780 :param context: optional context arguments, like lang, time zone
1781 :type context: dictionary
1782 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1783 :return: id or list of ids of records matching the criteria
1784 :rtype: integer or list of integers
1785 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1787 **Expressing a search domain (args)**
1789 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1791 * **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.
1792 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1793 The semantics of most of these operators are obvious.
1794 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1795 according to the semantics of this model (i.e following the relationship field named by
1796 ``self._parent_name``, by default ``parent_id``.
1797 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1799 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1800 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1801 Be very careful about this when you combine them the first time.
1803 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1805 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1807 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::
1809 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1812 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1814 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1816 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1817 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1818 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1820 :param access_rights_uid: optional user ID to use when checking access rights
1821 (not for ir.rules, this is only for ir.model.access)
1823 raise NotImplementedError(_('The search method is not implemented on this object !'))
1825 def name_get(self, cr, user, ids, context=None):
1828 :param cr: database cursor
1829 :param user: current user id
1831 :param ids: list of ids
1832 :param context: context arguments, like lang, time zone
1833 :type context: dictionary
1834 :return: tuples with the text representation of requested objects for to-many relationships
1841 if isinstance(ids, (int, long)):
1843 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1844 [self._rec_name], context, load='_classic_write')]
1846 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1848 Search for records and their display names according to a search domain.
1850 :param cr: database cursor
1851 :param user: current user id
1852 :param name: object name to search
1853 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1854 :param operator: operator for search criterion
1855 :param context: context arguments, like lang, time zone
1856 :type context: dictionary
1857 :param limit: optional max number of records to return
1858 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1860 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1861 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1864 return self._name_search(cr, user, name, args, operator, context, limit)
1866 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1867 # solve some access rights issues
1868 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1875 args += [(self._rec_name, operator, name)]
1876 access_rights_uid = name_get_uid or user
1877 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1878 res = self.name_get(cr, access_rights_uid, ids, context)
1881 def copy(self, cr, uid, id, default=None, context=None):
1882 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1884 def exists(self, cr, uid, id, context=None):
1885 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1887 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1890 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1892 fields = self._columns.keys() + self._inherit_fields.keys()
1893 #FIXME: collect all calls to _get_source into one SQL call.
1895 res[lang] = {'code': lang}
1897 if f in self._columns:
1898 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1900 res[lang][f] = res_trans
1902 res[lang][f] = self._columns[f].string
1903 for table in self._inherits:
1904 cols = intersect(self._inherit_fields.keys(), fields)
1905 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1908 res[lang]['code'] = lang
1909 for f in res2[lang]:
1910 res[lang][f] = res2[lang][f]
1913 def write_string(self, cr, uid, id, langs, vals, context=None):
1914 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1915 #FIXME: try to only call the translation in one SQL
1918 if field in self._columns:
1919 src = self._columns[field].string
1920 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1921 for table in self._inherits:
1922 cols = intersect(self._inherit_fields.keys(), vals)
1924 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1927 def _check_removed_columns(self, cr, log=False):
1928 raise NotImplementedError()
1930 def _add_missing_default_values(self, cr, uid, values, context=None):
1931 missing_defaults = []
1932 avoid_tables = [] # avoid overriding inherited values when parent is set
1933 for tables, parent_field in self._inherits.items():
1934 if parent_field in values:
1935 avoid_tables.append(tables)
1936 for field in self._columns.keys():
1937 if not field in values:
1938 missing_defaults.append(field)
1939 for field in self._inherit_fields.keys():
1940 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1941 missing_defaults.append(field)
1943 if len(missing_defaults):
1944 # override defaults with the provided values, never allow the other way around
1945 defaults = self.default_get(cr, uid, missing_defaults, context)
1947 # FIXME: also handle inherited m2m
1948 if dv in self._columns and self._columns[dv]._type == 'many2many' \
1949 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1950 defaults[dv] = [(6, 0, defaults[dv])]
1951 defaults.update(values)
1955 class orm_memory(orm_template):
1957 _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']
1958 _inherit_fields = {}
1963 def __init__(self, cr):
1964 super(orm_memory, self).__init__(cr)
1968 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1970 def _check_access(self, uid, object_id, mode):
1971 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1972 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1974 def vaccum(self, cr, uid):
1976 if self.check_id % self._check_time:
1979 max = time.time() - self._max_hours * 60 * 60
1980 for id in self.datas:
1981 if self.datas[id]['internal.date_access'] < max:
1983 self.unlink(cr, 1, tounlink)
1984 if len(self.datas)>self._max_count:
1985 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1987 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1988 self.unlink(cr, uid, ids)
1991 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1994 if not fields_to_read:
1995 fields_to_read = self._columns.keys()
1999 if isinstance(ids, (int, long)):
2003 for f in fields_to_read:
2004 record = self.datas.get(id)
2006 self._check_access(user, id, 'read')
2007 r[f] = record.get(f, False)
2008 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2011 if id in self.datas:
2012 self.datas[id]['internal.date_access'] = time.time()
2013 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2014 for f in fields_post:
2015 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2016 for record in result:
2017 record[f] = res2[record['id']]
2018 if isinstance(ids_orig, (int, long)):
2022 def write(self, cr, user, ids, vals, context=None):
2028 if self._columns[field]._classic_write:
2029 vals2[field] = vals[field]
2031 upd_todo.append(field)
2032 for object_id in ids:
2033 self._check_access(user, object_id, mode='write')
2034 self.datas[object_id].update(vals2)
2035 self.datas[object_id]['internal.date_access'] = time.time()
2036 for field in upd_todo:
2037 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2038 self._validate(cr, user, [object_id], context)
2039 wf_service = netsvc.LocalService("workflow")
2040 wf_service.trg_write(user, self._name, object_id, cr)
2043 def create(self, cr, user, vals, context=None):
2044 self.vaccum(cr, user)
2046 id_new = self.next_id
2048 vals = self._add_missing_default_values(cr, user, vals, context)
2053 if self._columns[field]._classic_write:
2054 vals2[field] = vals[field]
2056 upd_todo.append(field)
2057 self.datas[id_new] = vals2
2058 self.datas[id_new]['internal.date_access'] = time.time()
2059 self.datas[id_new]['internal.create_uid'] = user
2061 for field in upd_todo:
2062 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2063 self._validate(cr, user, [id_new], context)
2064 if self._log_create and not (context and context.get('no_store_function', False)):
2065 message = self._description + \
2067 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2069 self.log(cr, user, id_new, message, True, context=context)
2070 wf_service = netsvc.LocalService("workflow")
2071 wf_service.trg_create(user, self._name, id_new, cr)
2074 def _where_calc(self, cr, user, args, active_test=True, context=None):
2079 # if the object has a field named 'active', filter out all inactive
2080 # records unless they were explicitely asked for
2081 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2083 active_in_args = False
2085 if a[0] == 'active':
2086 active_in_args = True
2087 if not active_in_args:
2088 args.insert(0, ('active', '=', 1))
2090 args = [('active', '=', 1)]
2093 e = expression.expression(args)
2094 e.parse(cr, user, self, context)
2098 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2102 # implicit filter on current user except for superuser
2106 args.insert(0, ('internal.create_uid', '=', user))
2108 result = self._where_calc(cr, user, args, context=context)
2110 return self.datas.keys()
2114 #Find the value of dict
2117 for id, data in self.datas.items():
2120 if limit and (counter > int(limit)):
2125 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2126 elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
2127 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2128 elif arg[1] in ['ilike']:
2129 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2139 def unlink(self, cr, uid, ids, context=None):
2141 self._check_access(uid, id, 'unlink')
2142 self.datas.pop(id, None)
2144 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2147 def perm_read(self, cr, user, ids, context=None, details=True):
2149 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2150 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2152 self._check_access(user, id, 'read')
2154 'create_uid': credentials,
2155 'create_date': create_date,
2157 'write_date': False,
2162 def _check_removed_columns(self, cr, log=False):
2163 # nothing to check in memory...
2166 def exists(self, cr, uid, id, context=None):
2167 return id in self.datas
2169 class orm(orm_template):
2170 _sql_constraints = []
2172 _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']
2174 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2176 Get the list of records in list view grouped by the given ``groupby`` fields
2178 :param cr: database cursor
2179 :param uid: current user id
2180 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2181 :param fields: list of fields present in the list view specified on the object
2182 :param groupby: list of fields on which to groupby the records
2183 :type fields_list: list (example ['field_name_1', ...])
2184 :param offset: optional number of records to skip
2185 :param limit: optional max number of records to return
2186 :param context: context arguments, like lang, time zone
2187 :return: list of dictionaries(one dictionary for each record) containing:
2189 * the values of fields grouped by the fields in ``groupby`` argument
2190 * __domain: list of tuples specifying the search criteria
2191 * __context: dictionary with argument like ``groupby``
2192 :rtype: [{'field_name_1': value, ...]
2193 :raise AccessError: * if user has no read rights on the requested object
2194 * if user tries to bypass access rules for read on the requested object
2197 context = context or {}
2198 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2200 fields = self._columns.keys()
2202 # compute the where, order by, limit and offset clauses
2203 (where_clause, where_clause_params, tables) = self._where_calc(cr, uid, domain, context=context)
2205 # apply direct ir.rules from current model
2206 self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', context=context)
2208 # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
2209 for inherited_model in self._inherits:
2210 previous_tables = list(tables)
2211 if self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
2212 # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
2213 # list of table in case the inherited table was not in the list before (as that means the corresponding
2214 # JOIN(s) was(were) not present)
2215 self._inherits_join_add(inherited_model, previous_tables, where_clause)
2216 tables = list(set(tables).union(set(previous_tables)))
2218 # Take care of adding join(s) if groupby is an '_inherits'ed field
2219 groupby_list = groupby
2221 if groupby and isinstance(groupby, list):
2222 groupby = groupby[0]
2223 tables, where_clause, qfield = self._inherits_join_calc(groupby,tables,where_clause)
2225 if len(where_clause):
2226 where_clause = ' where '+string.join(where_clause, ' and ')
2229 limit_str = limit and ' limit %d' % limit or ''
2230 offset_str = offset and ' offset %d' % offset or ''
2232 fget = self.fields_get(cr, uid, fields)
2233 float_int_fields = filter(lambda x: fget[x]['type'] in ('float','integer'), fields)
2239 if fget.get(groupby,False) and fget[groupby]['type'] in ('date','datetime'):
2240 flist = "to_char(%s,'yyyy-mm') as %s "%(groupby,groupby)
2241 groupby = "to_char(%s,'yyyy-mm')"%(groupby)
2246 fields_pre = [f for f in float_int_fields if
2247 f == self.CONCURRENCY_CHECK_FIELD
2248 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2249 for f in fields_pre:
2250 if f not in ['id','sequence']:
2251 operator = fget[f].get('group_operator','sum')
2254 flist += operator+'('+f+') as '+f
2257 gb = ' group by '+groupby
2260 cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_clause_params)
2263 for r in cr.dictfetchall():
2264 for fld,val in r.items():
2265 if val == None:r[fld] = False
2266 alldata[r['id']] = r
2268 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2271 d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
2272 if not isinstance(groupby_list,(str, unicode)):
2273 if groupby or not context.get('group_by_no_leaf', False):
2274 d['__context'] = {'group_by':groupby_list[1:]}
2275 if groupby and fget.has_key(groupby):
2276 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
2277 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
2278 days = calendar.monthrange(dt.year, dt.month)[1]
2280 d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
2281 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),\
2282 (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
2283 del alldata[d['id']][groupby]
2284 d.update(alldata[d['id']])
2288 def _inherits_join_add(self, parent_model_name, tables, where_clause):
2290 Add missing table SELECT and JOIN clause for reaching the parent table (no duplicates)
2292 :param parent_model_name: name of the parent model for which the clauses should be added
2293 :param tables: list of table._table names enclosed in double quotes as returned
2295 :param where_clause: current list of WHERE clause params
2297 inherits_field = self._inherits[parent_model_name]
2298 parent_model = self.pool.get(parent_model_name)
2299 parent_table_name = parent_model._table
2300 quoted_parent_table_name = '"%s"' % parent_table_name
2301 if quoted_parent_table_name not in tables:
2302 tables.append(quoted_parent_table_name)
2303 where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2304 return (tables, where_clause)
2306 def _inherits_join_calc(self, field, tables, where_clause):
2308 Adds missing table select and join clause(s) for reaching
2309 the field coming from an '_inherits' parent table (no duplicates).
2311 :param tables: list of table._table names enclosed in double quotes as returned
2313 :param where_clause: current list of WHERE clause params
2314 :return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated
2315 versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
2316 in the form ``table.field``, to be referenced in queries.
2318 current_table = self
2319 while field in current_table._inherit_fields and not field in current_table._columns:
2320 parent_model_name = current_table._inherit_fields[field][0]
2321 parent_table = self.pool.get(parent_model_name)
2322 self._inherits_join_add(parent_model_name, tables, where_clause)
2323 current_table = parent_table
2324 return (tables, where_clause, '"%s".%s' % (current_table._table, field))
2326 def _parent_store_compute(self, cr):
2327 if not self._parent_store:
2329 logger = netsvc.Logger()
2330 logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2331 def browse_rec(root, pos=0):
2333 where = self._parent_name+'='+str(root)
2335 where = self._parent_name+' IS NULL'
2336 if self._parent_order:
2337 where += ' order by '+self._parent_order
2338 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2340 childs = cr.fetchall()
2342 pos2 = browse_rec(id[0], pos2)
2343 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
2345 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2346 if self._parent_order:
2347 query += ' order by '+self._parent_order
2350 for (root,) in cr.fetchall():
2351 pos = browse_rec(root, pos)
2354 def _update_store(self, cr, f, k):
2355 logger = netsvc.Logger()
2356 logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2357 ss = self._columns[k]._symbol_set
2358 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2359 cr.execute('select id from '+self._table)
2360 ids_lst = map(lambda x: x[0], cr.fetchall())
2363 ids_lst = ids_lst[40:]
2364 res = f.get(cr, self, iids, k, 1, {})
2365 for key,val in res.items():
2368 # if val is a many2one, just write the ID
2369 if type(val)==tuple:
2371 if (val<>False) or (type(val)<>bool):
2372 cr.execute(update_query, (ss[1](val), key))
2374 def _check_removed_columns(self, cr, log=False):
2375 logger = netsvc.Logger()
2376 # iterate on the database columns to drop the NOT NULL constraints
2377 # of fields which were required but have been removed (or will be added by another module)
2378 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2379 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2380 cr.execute("SELECT a.attname, a.attnotnull"
2381 " FROM pg_class c, pg_attribute a"
2382 " WHERE c.relname=%s"
2383 " AND c.oid=a.attrelid"
2384 " AND a.attisdropped=%s"
2385 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2386 " AND a.attname NOT IN %s" ,(self._table, False, tuple(columns))),
2388 for column in cr.dictfetchall():
2390 logger.notifyChannel("orm", netsvc.LOG_DEBUG, "column %s is in the table %s but not in the corresponding object %s" % (column['attname'], self._table, self._name))
2391 if column['attnotnull']:
2392 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2394 def _auto_init(self, cr, context={}):
2395 store_compute = False
2396 logger = netsvc.Logger()
2399 self._field_create(cr, context=context)
2400 if getattr(self, '_auto', True):
2401 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s" ,( self._table,))
2403 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2404 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2407 if self._parent_store:
2408 cr.execute("""SELECT c.relname
2409 FROM pg_class c, pg_attribute a
2410 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2411 """, (self._table, 'parent_left'))
2413 if 'parent_left' not in self._columns:
2414 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2415 if 'parent_right' not in self._columns:
2416 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2417 if self._columns[self._parent_name].ondelete != 'cascade':
2418 logger.notifyChannel('orm', netsvc.LOG_ERROR, "The column %s on object %s must be set as ondelete='cascade'" % (self._parent_name, self._name))
2419 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2420 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2422 store_compute = True
2424 if self._log_access:
2426 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2427 'create_date': 'TIMESTAMP',
2428 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2429 'write_date': 'TIMESTAMP'
2434 FROM pg_class c, pg_attribute a
2435 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2436 """, (self._table, k))
2438 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2441 self._check_removed_columns(cr, log=False)
2443 # iterate on the "object columns"
2444 todo_update_store = []
2445 update_custom_fields = context.get('update_custom_fields', False)
2446 for k in self._columns:
2447 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2449 #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2450 #Not Updating Custom fields
2451 if k.startswith('x_') and not update_custom_fields:
2453 f = self._columns[k]
2455 if isinstance(f, fields.one2many):
2456 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2458 if self.pool.get(f._obj):
2459 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2460 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2461 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2464 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))
2465 res = cr.fetchone()[0]
2467 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2468 elif isinstance(f, fields.many2many):
2469 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2470 if not cr.dictfetchall():
2471 if not self.pool.get(f._obj):
2472 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2473 ref = self.pool.get(f._obj)._table
2474 # ref = f._obj.replace('.', '_')
2475 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))
2476 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2477 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2478 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2481 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 " \
2482 "FROM pg_class c,pg_attribute a,pg_type t " \
2483 "WHERE c.relname=%s " \
2484 "AND a.attname=%s " \
2485 "AND c.oid=a.attrelid " \
2486 "AND a.atttypid=t.oid", (self._table, k))
2487 res = cr.dictfetchall()
2488 if not res and hasattr(f,'oldname'):
2489 cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
2490 "FROM pg_class c,pg_attribute a,pg_type t " \
2491 "WHERE c.relname=%s " \
2492 "AND a.attname=%s " \
2493 "AND c.oid=a.attrelid " \
2494 "AND a.atttypid=t.oid", (self._table, f.oldname))
2495 res_old = cr.dictfetchall()
2496 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2497 if res_old and len(res_old)==1:
2498 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2500 res[0]['attname'] = k
2505 f_pg_type = f_pg_def['typname']
2506 f_pg_size = f_pg_def['size']
2507 f_pg_notnull = f_pg_def['attnotnull']
2508 if isinstance(f, fields.function) and not f.store and\
2509 not getattr(f, 'nodrop', False):
2510 logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2511 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2515 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2520 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2521 ('varchar', 'text', 'TEXT', ''),
2522 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2523 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2524 ('timestamp', 'date', 'date', '::date'),
2525 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2526 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2528 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2529 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2530 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2531 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2532 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2533 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2536 if (f_pg_type==c[0]) and (f._type==c[1]):
2537 if f_pg_type != f_obj_type:
2538 if f_pg_type != f_obj_type:
2539 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2541 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2542 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2543 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2544 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2548 if f_pg_type != f_obj_type:
2552 newname = self._table + '_moved' + str(i)
2553 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2554 "WHERE c.relname=%s " \
2555 "AND a.attname=%s " \
2556 "AND c.oid=a.attrelid ", (self._table, newname))
2557 if not cr.fetchone()[0]:
2560 logger.notifyChannel('orm', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed type (DB=%s, def=%s), data moved to table %s !" % (k, self._table, f_pg_type, f._type, newname))
2562 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2563 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2564 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2565 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2567 # if the field is required and hasn't got a NOT NULL constraint
2568 if f.required and f_pg_notnull == 0:
2569 # set the field to the default value if any
2570 if k in self._defaults:
2571 if callable(self._defaults[k]):
2572 default = self._defaults[k](self, cr, 1, context)
2574 default = self._defaults[k]
2576 if (default is not None):
2577 ss = self._columns[k]._symbol_set
2578 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2579 cr.execute(query, (ss[1](default),))
2580 # add the NOT NULL constraint
2583 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2586 logger.notifyChannel('orm', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
2588 elif not f.required and f_pg_notnull == 1:
2589 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2593 indexname = '%s_%s_index' % (self._table, k)
2594 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2595 res2 = cr.dictfetchall()
2596 if not res2 and f.select:
2597 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2599 if f._type == 'text':
2600 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2601 logger.notifyChannel('orm', netsvc.LOG_WARNING, "Adding (b-tree) index for text column '%s' in table '%s'."\
2602 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts because there is a length limit for indexable btree values!\n"\
2603 "Use a search view instead if you simply want to make the field searchable." % (k, f._type, self._table))
2604 if res2 and not f.select:
2605 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2607 logger.notifyChannel('orm', netsvc.LOG_WARNING, "Dropping index for column '%s' of type '%s' in table '%s' as it is not required anymore" % (k, f._type, self._table))
2609 if isinstance(f, fields.many2one):
2610 ref = self.pool.get(f._obj)._table
2611 if ref != 'ir_actions':
2612 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2613 'pg_attribute as att1, pg_attribute as att2 '
2614 'WHERE con.conrelid = cl1.oid '
2615 'AND cl1.relname = %s '
2616 'AND con.confrelid = cl2.oid '
2617 'AND cl2.relname = %s '
2618 'AND array_lower(con.conkey, 1) = 1 '
2619 'AND con.conkey[1] = att1.attnum '
2620 'AND att1.attrelid = cl1.oid '
2621 'AND att1.attname = %s '
2622 'AND array_lower(con.confkey, 1) = 1 '
2623 'AND con.confkey[1] = att2.attnum '
2624 'AND att2.attrelid = cl2.oid '
2625 'AND att2.attname = %s '
2626 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2627 res2 = cr.dictfetchall()
2629 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2630 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2631 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2634 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2636 if not isinstance(f, fields.function) or f.store:
2638 # add the missing field
2639 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2640 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2643 if not create and k in self._defaults:
2644 if callable(self._defaults[k]):
2645 default = self._defaults[k](self, cr, 1, context)
2647 default = self._defaults[k]
2649 ss = self._columns[k]._symbol_set
2650 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2651 cr.execute(query, (ss[1](default),))
2653 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2655 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2657 if isinstance(f, fields.function):
2659 if f.store is not True:
2660 order = f.store[f.store.keys()[0]][2]
2661 todo_update_store.append((order, f,k))
2663 # and add constraints if needed
2664 if isinstance(f, fields.many2one):
2665 if not self.pool.get(f._obj):
2666 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2667 ref = self.pool.get(f._obj)._table
2668 # ref = f._obj.replace('.', '_')
2669 # ir_actions is inherited so foreign key doesn't work on it
2670 if ref != 'ir_actions':
2671 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2673 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2677 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2679 logger.notifyChannel('orm', netsvc.LOG_WARNING, 'WARNING: unable to set column %s of table %s not null !\nTry to re-run: openerp-server.py --update=module\nIf it doesn\'t work, update records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
2681 for order,f,k in todo_update_store:
2682 todo_end.append((order, self._update_store, (f, k)))
2685 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2686 create = not bool(cr.fetchone())
2688 cr.commit() # start a new transaction
2690 for (key, con, _) in self._sql_constraints:
2691 conname = '%s_%s' % (self._table, key)
2692 cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2693 if not cr.dictfetchall():
2694 query = 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,)
2699 logger.notifyChannel('orm', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\n%s' % (con, self._table, query))
2703 if hasattr(self, "_sql"):
2704 for line in self._sql.split(';'):
2705 line2 = line.replace('\n', '').strip()
2710 self._parent_store_compute(cr)
2714 def __init__(self, cr):
2715 super(orm, self).__init__(cr)
2717 if not hasattr(self, '_log_access'):
2718 # if not access is not specify, it is the same value as _auto
2719 self._log_access = getattr(self, "_auto", True)
2721 self._columns = self._columns.copy()
2722 for store_field in self._columns:
2723 f = self._columns[store_field]
2724 if hasattr(f, 'digits_change'):
2726 if not isinstance(f, fields.function):
2730 if self._columns[store_field].store is True:
2731 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2733 sm = self._columns[store_field].store
2734 for object, aa in sm.items():
2736 (fnct,fields2,order,length)=aa
2738 (fnct,fields2,order)=aa
2741 raise except_orm('Error',
2742 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2743 self.pool._store_function.setdefault(object, [])
2745 for x,y,z,e,f,l in self.pool._store_function[object]:
2746 if (x==self._name) and (y==store_field) and (e==fields2):
2750 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2751 self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2753 for (key, _, msg) in self._sql_constraints:
2754 self.pool._sql_error[self._table+'_'+key] = msg
2756 # Load manual fields
2758 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2760 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2761 for field in cr.dictfetchall():
2762 if field['name'] in self._columns:
2765 'string': field['field_description'],
2766 'required': bool(field['required']),
2767 'readonly': bool(field['readonly']),
2768 'domain': field['domain'] or None,
2769 'size': field['size'],
2770 'ondelete': field['on_delete'],
2771 'translate': (field['translate']),
2772 #'select': int(field['select_level'])
2775 if field['ttype'] == 'selection':
2776 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2777 elif field['ttype'] == 'reference':
2778 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2779 elif field['ttype'] == 'many2one':
2780 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2781 elif field['ttype'] == 'one2many':
2782 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2783 elif field['ttype'] == 'many2many':
2784 _rel1 = field['relation'].replace('.', '_')
2785 _rel2 = field['model'].replace('.', '_')
2786 _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2787 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2789 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2790 self._inherits_check()
2791 self._inherits_reload()
2792 if not self._sequence:
2793 self._sequence = self._table+'_id_seq'
2794 for k in self._defaults:
2795 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,)
2796 for f in self._columns:
2797 self._columns[f].restart()
2800 # Update objects that uses this one to update their _inherits fields
2803 def _inherits_reload_src(self):
2804 for obj in self.pool.obj_pool.values():
2805 if self._name in obj._inherits:
2806 obj._inherits_reload()
2808 def _inherits_reload(self):
2810 for table in self._inherits:
2811 res.update(self.pool.get(table)._inherit_fields)
2812 for col in self.pool.get(table)._columns.keys():
2813 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2814 for col in self.pool.get(table)._inherit_fields.keys():
2815 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2816 self._inherit_fields = res
2817 self._inherits_reload_src()
2819 def _inherits_check(self):
2820 for table, field_name in self._inherits.items():
2821 if field_name not in self._columns:
2822 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2823 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2824 required=True, ondelete="cascade")
2825 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2826 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))
2827 self._columns[field_name].required = True
2828 self._columns[field_name].ondelete = "cascade"
2830 #def __getattr__(self, name):
2832 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2833 # (though inherits doesn't use Python inheritance).
2834 # Handles translating between local ids and remote ids.
2835 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2836 # when you have inherits.
2838 # for model, field in self._inherits.iteritems():
2839 # proxy = self.pool.get(model)
2840 # if hasattr(proxy, name):
2841 # attribute = getattr(proxy, name)
2842 # if not hasattr(attribute, '__call__'):
2846 # return super(orm, self).__getattr__(name)
2848 # def _proxy(cr, uid, ids, *args, **kwargs):
2849 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2850 # lst = [obj[field].id for obj in objects if obj[field]]
2851 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2856 def fields_get(self, cr, user, fields=None, context=None):
2858 Get the description of list of fields
2860 :param cr: database cursor
2861 :param user: current user id
2862 :param fields: list of fields
2863 :param context: context arguments, like lang, time zone
2864 :return: dictionary of field dictionaries, each one describing a field of the business object
2865 :raise AccessError: * if user has no create/write rights on the requested object
2868 ira = self.pool.get('ir.model.access')
2869 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2870 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2871 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2873 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2875 Read records with given ids with the given fields
2877 :param cr: database cursor
2878 :param user: current user id
2879 :param ids: id or list of the ids of the records to read
2880 :param fields: optional list of field names to return (default: all fields would be returned)
2881 :type fields: list (example ['field_name_1', ...])
2882 :param context: (optional) context arguments, like lang, time zone
2883 :return: list of dictionaries((dictionary per record asked)) with requested field values
2884 :rtype: [{‘name_of_the_field’: value, ...}, ...]
2885 :raise AccessError: * if user has no read rights on the requested object
2886 * if user tries to bypass access rules for read on the requested object
2891 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2893 fields = self._columns.keys() + self._inherit_fields.keys()
2894 if isinstance(ids, (int, long)):
2898 select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2899 result = self._read_flat(cr, user, select, fields, context, load)
2902 for key, v in r.items():
2905 if key in self._columns:
2906 column = self._columns[key]
2907 elif key in self._inherit_fields:
2908 column = self._inherit_fields[key][2]
2911 if v and column._type == 'reference':
2912 model_name, ref_id = v.split(',', 1)
2913 model = self.pool.get(model_name)
2917 cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
2918 reset = not cr.fetchone()[0]
2920 if column._classic_write:
2921 query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
2922 cr.execute(query, (r['id'],))
2925 if isinstance(ids, (int, long, dict)):
2926 return result and result[0] or False
2929 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2934 if fields_to_read == None:
2935 fields_to_read = self._columns.keys()
2937 # Construct a clause for the security rules.
2938 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2939 # or will at least contain self._table.
2940 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2942 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2943 fields_pre = [f for f in fields_to_read if
2944 f == self.CONCURRENCY_CHECK_FIELD
2945 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2946 ] + self._inherits.values()
2950 def convert_field(f):
2951 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2952 if f in ('create_date', 'write_date'):
2953 return "date_trunc('second', %s) as %s" % (f_qual, f)
2954 if f == self.CONCURRENCY_CHECK_FIELD:
2955 if self._log_access:
2956 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2957 return "now()::timestamp AS %s" % (f,)
2958 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2959 return 'length(%s) as "%s"' % (f_qual, f)
2962 fields_pre2 = map(convert_field, fields_pre)
2963 order_by = self._parent_order or self._order
2964 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2965 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2967 query += " AND " + (' OR '.join(rule_clause))
2968 query += " ORDER BY " + order_by
2969 for sub_ids in cr.split_for_in_conditions(ids):
2971 cr.execute(query, [tuple(sub_ids)] + rule_params)
2972 if cr.rowcount != len(sub_ids):
2973 raise except_orm(_('AccessError'),
2974 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2976 cr.execute(query, (tuple(sub_ids),))
2977 res.extend(cr.dictfetchall())
2979 res = map(lambda x: {'id': x}, ids)
2981 for f in fields_pre:
2982 if f == self.CONCURRENCY_CHECK_FIELD:
2984 if self._columns[f].translate:
2985 ids = [x['id'] for x in res]
2986 #TODO: optimize out of this loop
2987 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2989 r[f] = res_trans.get(r['id'], False) or r[f]
2991 for table in self._inherits:
2992 col = self._inherits[table]
2993 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
2996 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3004 if not record[col]:# if the record is deleted from _inherits table?
3006 record.update(res3[record[col]])
3007 if col not in fields_to_read:
3010 # all fields which need to be post-processed by a simple function (symbol_get)
3011 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3014 for f in fields_post:
3015 r[f] = self._columns[f]._symbol_get(r[f])
3016 ids = [x['id'] for x in res]
3018 # all non inherited fields for which the attribute whose name is in load is False
3019 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3021 # Compute POST fields
3023 for f in fields_post:
3024 todo.setdefault(self._columns[f]._multi, [])
3025 todo[self._columns[f]._multi].append(f)
3026 for key,val in todo.items():
3028 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3031 if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3032 record[pos] = res2[record['id']][pos]
3035 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3038 record[f] = res2[record['id']]
3043 for field in vals.copy():
3045 if field in self._columns:
3046 fobj = self._columns[field]
3053 for group in groups:
3054 module = group.split(".")[0]
3055 grp = group.split(".")[1]
3056 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" \
3057 (grp, module, 'res.groups', user))
3058 readonly = cr.fetchall()
3059 if readonly[0][0] >= 1:
3062 elif readonly[0][0] == 0:
3068 if type(vals[field]) == type([]):
3070 elif type(vals[field]) == type(0.0):
3072 elif type(vals[field]) == type(''):
3073 vals[field] = '=No Permission='
3078 def perm_read(self, cr, user, ids, context=None, details=True):
3080 Read the permission for record of the given ids
3082 :param cr: database cursor
3083 :param user: current user id
3084 :param ids: id or list of ids
3085 :param context: context arguments, like lang, time zone
3086 :param details: if True, \*_uid fields are replaced with the name of the user
3087 :return: list of ownership dictionaries for each requested record
3088 :rtype: list of dictionaries with the following keys:
3091 * create_uid: user who created the record
3092 * create_date: date when the record was created
3093 * write_uid: last user who changed the record
3094 * write_date: date of the last change to the record
3102 uniq = isinstance(ids, (int, long))
3106 if self._log_access:
3107 fields += ', create_uid, create_date, write_uid, write_date'
3108 query = 'SELECT %s FROM "%s" WHERE id IN %%s' % (fields, self._table)
3109 cr.execute(query, (tuple(ids),))
3110 res = cr.dictfetchall()
3113 r[key] = r[key] or False
3114 if key in ('write_uid', 'create_uid', 'uid') and details:
3116 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3121 def _check_concurrency(self, cr, ids, context):
3124 if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
3126 return "%s,%s" % (self._name, oid)
3127 santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3128 for i in range(0, len(ids), cr.IN_MAX):
3129 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
3130 for oid in ids[i:i+cr.IN_MAX]
3131 if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
3133 cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
3136 raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
3138 def check_access_rule(self, cr, uid, ids, operation, context=None):
3139 """Verifies that the operation given by ``operation`` is allowed for the user
3140 according to ir.rules.
3142 :param operation: one of ``write``, ``unlink``
3143 :raise except_orm: * if current ir.rules do not permit this operation.
3144 :return: None if the operation is allowed
3146 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3148 where_clause = ' and ' + ' and '.join(where_clause)
3149 for sub_ids in cr.split_for_in_conditions(ids):
3150 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3151 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3152 [sub_ids] + where_params)
3153 if cr.rowcount != len(sub_ids):
3154 raise except_orm(_('AccessError'),
3155 _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
3156 % (operation, self._name))
3158 def unlink(self, cr, uid, ids, context=None):
3160 Delete records with given ids
3162 :param cr: database cursor
3163 :param uid: current user id
3164 :param ids: id or list of ids
3165 :param context: (optional) context arguments, like lang, time zone
3167 :raise AccessError: * if user has no unlink rights on the requested object
3168 * if user tries to bypass access rules for unlink on the requested object
3169 :raise UserError: if the record is default property for other records
3174 if isinstance(ids, (int, long)):
3177 result_store = self._store_get_values(cr, uid, ids, None, context)
3179 self._check_concurrency(cr, ids, context)
3181 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3183 properties = self.pool.get('ir.property')
3184 domain = [('res_id', '=', False),
3185 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3187 if properties.search(cr, uid, domain, context=context):
3188 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3190 wf_service = netsvc.LocalService("workflow")
3192 wf_service.trg_delete(uid, self._name, oid, cr)
3195 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3196 for sub_ids in cr.split_for_in_conditions(ids):
3197 cr.execute('delete from ' + self._table + ' ' \
3198 'where id IN %s', (sub_ids,))
3199 for order, object, store_ids, fields in result_store:
3200 if object != self._name:
3201 obj = self.pool.get(object)
3202 cr.execute('select id from '+obj._table+' where id IN %s',(tuple(store_ids),))
3203 rids = map(lambda x: x[0], cr.fetchall())
3205 obj._store_set_values(cr, uid, rids, fields, context)
3211 def write(self, cr, user, ids, vals, context=None):
3213 Update records with given ids with the given field values
3215 :param cr: database cursor
3216 :param user: current user id
3218 :param ids: object id or list of object ids to update according to **vals**
3219 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3220 :type vals: dictionary
3221 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3222 :type context: dictionary
3224 :raise AccessError: * if user has no write rights on the requested object
3225 * if user tries to bypass access rules for write on the requested object
3226 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3227 :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)
3229 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3231 + For a many2many field, a list of tuples is expected.
3232 Here is the list of tuple that are accepted, with the corresponding semantics ::
3234 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3235 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3236 (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)
3237 (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)
3238 (4, ID) link to existing record with id = ID (adds a relationship)
3239 (5) unlink all (like using (3,ID) for all linked records)
3240 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3243 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3245 + For a one2many field, a lits of tuples is expected.
3246 Here is the list of tuple that are accepted, with the corresponding semantics ::
3248 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3249 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3250 (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)
3253 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3255 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3256 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3260 for field in vals.copy():
3262 if field in self._columns:
3263 fobj = self._columns[field]
3265 fobj = self._inherit_fields[field][2]
3272 for group in groups:
3273 module = group.split(".")[0]
3274 grp = group.split(".")[1]
3275 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" \
3276 (grp, module, 'res.groups', user))
3277 readonly = cr.fetchall()
3278 if readonly[0][0] >= 1:
3281 elif readonly[0][0] == 0:
3293 if isinstance(ids, (int, long)):
3296 self._check_concurrency(cr, ids, context)
3297 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3299 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3301 # No direct update of parent_left/right
3302 vals.pop('parent_left', None)
3303 vals.pop('parent_right', None)
3305 parents_changed = []
3306 if self._parent_store and (self._parent_name in vals):
3307 # The parent_left/right computation may take up to
3308 # 5 seconds. No need to recompute the values if the
3309 # parent is the same. Get the current value of the parent
3310 parent_val = vals[self._parent_name]
3312 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3313 (self._table, self._parent_name, self._parent_name)
3314 cr.execute(query, (tuple(ids), parent_val))
3316 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3317 (self._table, self._parent_name)
3318 cr.execute(query, (tuple(ids),))
3319 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3326 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3328 if field in self._columns:
3329 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3330 if (not totranslate) or not self._columns[field].translate:
3331 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3332 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3333 direct.append(field)
3335 upd_todo.append(field)
3337 updend.append(field)
3338 if field in self._columns \
3339 and hasattr(self._columns[field], 'selection') \
3341 if self._columns[field]._type == 'reference':
3342 val = vals[field].split(',')[0]
3345 if isinstance(self._columns[field].selection, (tuple, list)):
3346 if val not in dict(self._columns[field].selection):
3347 raise except_orm(_('ValidateError'),
3348 _('The value "%s" for the field "%s" is not in the selection') \
3349 % (vals[field], field))
3351 if val not in dict(self._columns[field].selection(
3352 self, cr, user, context=context)):
3353 raise except_orm(_('ValidateError'),
3354 _('The value "%s" for the field "%s" is not in the selection') \
3355 % (vals[field], field))
3357 if self._log_access:
3358 upd0.append('write_uid=%s')
3359 upd0.append('write_date=now()')
3363 self.check_access_rule(cr, user, ids, 'write', context=context)
3364 for sub_ids in cr.split_for_in_conditions(ids):
3365 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3366 'where id IN %s', upd1 + [sub_ids])
3371 if self._columns[f].translate:
3372 src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
3375 # Inserting value to DB
3376 self.write(cr, user, ids, {f:vals[f]})
3377 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3380 # call the 'set' method of fields which are not classic_write
3381 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3383 # default element in context must be removed when call a one2many or many2many
3384 rel_context = context.copy()
3385 for c in context.items():
3386 if c[0].startswith('default_'):
3387 del rel_context[c[0]]
3389 for field in upd_todo:
3391 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3393 for table in self._inherits:
3394 col = self._inherits[table]
3396 for sub_ids in cr.split_for_in_conditions(ids):
3397 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3398 'where id IN %s', (sub_ids,))
3399 nids.extend([x[0] for x in cr.fetchall()])
3403 if self._inherit_fields[val][0] == table:
3405 self.pool.get(table).write(cr, user, nids, v, context)
3407 self._validate(cr, user, ids, context)
3409 # TODO: use _order to set dest at the right position and not first node of parent
3410 # We can't defer parent_store computation because the stored function
3411 # fields that are computer may refer (directly or indirectly) to
3412 # parent_left/right (via a child_of domain)
3415 self.pool._init_parent[self._name]=True
3417 order = self._parent_order or self._order
3418 parent_val = vals[self._parent_name]
3420 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3422 clause, params = '%s IS NULL' % (self._parent_name,), ()
3423 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3424 parents = cr.fetchall()
3426 for id in parents_changed:
3427 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3428 pleft, pright = cr.fetchone()
3429 distance = pright - pleft + 1
3431 # Find Position of the element
3433 for (parent_pright, parent_id) in parents:
3436 position = parent_pright+1
3438 # It's the first node of the parent
3443 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3444 position = cr.fetchone()[0]+1
3446 if pleft < position <= pright:
3447 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3449 if pleft < position:
3450 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3451 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3452 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))
3454 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3455 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3456 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))
3458 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3462 for order, object, ids, fields in result:
3463 key = (object,tuple(fields))
3464 done.setdefault(key, {})
3465 # avoid to do several times the same computation
3468 if id not in done[key]:
3469 done[key][id] = True
3471 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3473 wf_service = netsvc.LocalService("workflow")
3475 wf_service.trg_write(user, self._name, id, cr)
3479 # TODO: Should set perm to user.xxx
3481 def create(self, cr, user, vals, context=None):
3483 Create new record with specified value
3485 :param cr: database cursor
3486 :param user: current user id
3488 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3489 :type vals: dictionary
3490 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3491 :type context: dictionary
3492 :return: id of new record created
3493 :raise AccessError: * if user has no create rights on the requested object
3494 * if user tries to bypass access rules for create on the requested object
3495 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3496 :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)
3498 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3499 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3505 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3507 vals = self._add_missing_default_values(cr, user, vals, context)
3510 for v in self._inherits:
3511 if self._inherits[v] not in vals:
3514 tocreate[v] = {'id' : vals[self._inherits[v]]}
3515 (upd0, upd1, upd2) = ('', '', [])
3517 for v in vals.keys():
3518 if v in self._inherit_fields:
3519 (table, col, col_detail) = self._inherit_fields[v]
3520 tocreate[table][v] = vals[v]
3523 if (v not in self._inherit_fields) and (v not in self._columns):
3526 # Try-except added to filter the creation of those records whose filds are readonly.
3527 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3529 cr.execute("SELECT nextval('"+self._sequence+"')")
3531 raise except_orm(_('UserError'),
3532 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3534 id_new = cr.fetchone()[0]
3535 for table in tocreate:
3536 if self._inherits[table] in vals:
3537 del vals[self._inherits[table]]
3539 record_id = tocreate[table].pop('id', None)
3541 if record_id is None or not record_id:
3542 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3544 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3546 upd0 += ','+self._inherits[table]
3548 upd2.append(record_id)
3550 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3551 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3553 for bool_field in bool_fields:
3554 if bool_field not in vals:
3555 vals[bool_field] = False
3557 for field in vals.copy():
3559 if field in self._columns:
3560 fobj = self._columns[field]
3562 fobj = self._inherit_fields[field][2]
3568 for group in groups:
3569 module = group.split(".")[0]
3570 grp = group.split(".")[1]
3571 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" % \
3572 (grp, module, 'res.groups', user))
3573 readonly = cr.fetchall()
3574 if readonly[0][0] >= 1:
3577 elif readonly[0][0] == 0:
3585 if self._columns[field]._classic_write:
3586 upd0 = upd0 + ',"' + field + '"'
3587 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3588 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3590 if not isinstance(self._columns[field], fields.related):
3591 upd_todo.append(field)
3592 if field in self._columns \
3593 and hasattr(self._columns[field], 'selection') \
3595 if self._columns[field]._type == 'reference':
3596 val = vals[field].split(',')[0]
3599 if isinstance(self._columns[field].selection, (tuple, list)):
3600 if val not in dict(self._columns[field].selection):
3601 raise except_orm(_('ValidateError'),
3602 _('The value "%s" for the field "%s" is not in the selection') \
3603 % (vals[field], field))
3605 if val not in dict(self._columns[field].selection(
3606 self, cr, user, context=context)):
3607 raise except_orm(_('ValidateError'),
3608 _('The value "%s" for the field "%s" is not in the selection') \
3609 % (vals[field], field))
3610 if self._log_access:
3611 upd0 += ',create_uid,create_date'
3614 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3615 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3616 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3618 if self._parent_store and not context.get('defer_parent_store_computation'):
3620 self.pool._init_parent[self._name]=True
3622 parent = vals.get(self._parent_name, False)
3624 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3626 result_p = cr.fetchall()
3627 for (pleft,) in result_p:
3632 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3633 pleft_old = cr.fetchone()[0]
3636 cr.execute('select max(parent_right) from '+self._table)
3637 pleft = cr.fetchone()[0] or 0
3638 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3639 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3640 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3642 # default element in context must be remove when call a one2many or many2many
3643 rel_context = context.copy()
3644 for c in context.items():
3645 if c[0].startswith('default_'):
3646 del rel_context[c[0]]
3649 for field in upd_todo:
3650 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3651 self._validate(cr, user, [id_new], context)
3653 if not context.get('no_store_function', False):
3654 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3657 for order, object, ids, fields2 in result:
3658 if not (object, ids, fields2) in done:
3659 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3660 done.append((object, ids, fields2))
3662 if self._log_create and not (context and context.get('no_store_function', False)):
3663 message = self._description + \
3665 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3667 self.log(cr, user, id_new, message, True, context=context)
3668 wf_service = netsvc.LocalService("workflow")
3669 wf_service.trg_create(user, self._name, id_new, cr)
3672 def _store_get_values(self, cr, uid, ids, fields, context):
3674 fncts = self.pool._store_function.get(self._name, [])
3675 for fnct in range(len(fncts)):
3680 for f in (fields or []):
3681 if f in fncts[fnct][3]:
3687 result.setdefault(fncts[fnct][0], {})
3689 # uid == 1 for accessing objects having rules defined on store fields
3690 ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3691 for id in filter(None, ids2):
3692 result[fncts[fnct][0]].setdefault(id, [])
3693 result[fncts[fnct][0]][id].append(fnct)
3695 for object in result:
3697 for id,fnct in result[object].items():
3698 k2.setdefault(tuple(fnct), [])
3699 k2[tuple(fnct)].append(id)
3700 for fnct,id in k2.items():
3701 dict.setdefault(fncts[fnct[0]][4],[])
3702 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3710 def _store_set_values(self, cr, uid, ids, fields, context):
3715 if self._log_access:
3716 cr.execute('select id,write_date from '+self._table+' where id IN %s',(tuple(ids),))
3720 field_dict.setdefault(r[0], [])
3721 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3722 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3723 for i in self.pool._store_function.get(self._name, []):
3725 up_write_date = write_date + datetime.timedelta(hours=i[5])
3726 if datetime.datetime.now() < up_write_date:
3728 field_dict[r[0]].append(i[1])
3734 if self._columns[f]._multi not in keys:
3735 keys.append(self._columns[f]._multi)
3736 todo.setdefault(self._columns[f]._multi, [])
3737 todo[self._columns[f]._multi].append(f)
3741 # uid == 1 for accessing objects having rules defined on store fields
3742 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3743 for id,value in result.items():
3745 for f in value.keys():
3746 if f in field_dict[id]:
3753 if self._columns[v]._type in ('many2one', 'one2one'):
3755 value[v] = value[v][0]
3758 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3759 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3762 cr.execute('update "' + self._table + '" set ' + \
3763 string.join(upd0, ',') + ' where id = %s', upd1)
3767 # uid == 1 for accessing objects having rules defined on store fields
3768 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3769 for r in result.keys():
3771 if r in field_dict.keys():
3772 if f in field_dict[r]:
3774 for id,value in result.items():
3775 if self._columns[f]._type in ('many2one', 'one2one'):
3780 cr.execute('update "' + self._table + '" set ' + \
3781 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3787 def perm_write(self, cr, user, ids, fields, context=None):
3788 raise NotImplementedError(_('This method does not exist anymore'))
3790 # TODO: ameliorer avec NULL
3791 def _where_calc(self, cr, user, args, active_test=True, context=None):
3792 """Computes the WHERE clause needed to implement an OpenERP domain.
3793 :param args: the domain to compute
3795 :param active_test: whether the default filtering of records with ``active``
3796 field set to ``False`` should be applied.
3797 :return: tuple with 3 elements: (where_clause, where_clause_params, tables) where
3798 ``where_clause`` contains a list of where clause elements (to be joined with 'AND'),
3799 ``where_clause_params`` is a list of parameters to be passed to the db layer
3800 for the where_clause expansion, and ``tables`` is the list of double-quoted
3801 table names that need to be included in the FROM clause.
3807 # if the object has a field named 'active', filter out all inactive
3808 # records unless they were explicitely asked for
3809 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3811 active_in_args = False
3813 if a[0] == 'active':
3814 active_in_args = True
3815 if not active_in_args:
3816 args.insert(0, ('active', '=', 1))
3818 args = [('active', '=', 1)]
3822 e = expression.expression(args)
3823 e.parse(cr, user, self, context)
3824 tables = e.get_tables()
3825 qu1, qu2 = e.to_sql()
3826 qu1 = qu1 and [qu1] or []
3828 qu1, qu2, tables = [], [], ['"%s"' % self._table]
3830 return (qu1, qu2, tables)
3832 def _check_qorder(self, word):
3833 if not regex_order.match(word):
3834 raise except_orm(_('AccessError'), _('Bad query.'))
3837 def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
3838 """Add what's missing in ``where_clause``, ``where_params``, ``tables`` to implement
3839 all appropriate ir.rules (on the current object but also from it's _inherits parents)
3841 :param where_clause: list with current elements of the WHERE clause (strings)
3842 :param where_clause_params: list with parameters for ``where_clause``
3843 :param tables: list with double-quoted names of the tables that are joined
3845 :param model_name: optional name of the model whose ir.rules should be applied (default:``self._name``)
3846 This could be useful for inheritance for example, but there is no provision to include
3847 the appropriate JOIN for linking the current model to the one referenced in model_name.
3848 :return: True if additional clauses where applied.
3850 added_clause, added_params, added_tables = self.pool.get('ir.rule').domain_get(cr, uid, model_name or self._name, mode, context=context)
3852 where_clause += added_clause
3853 where_clause_params += added_params
3854 for table in added_tables:
3855 if table not in tables:
3856 tables.append(table)
3860 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
3862 Private implementation of search() method, allowing specifying the uid to use for the access right check.
3863 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
3864 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
3865 This is ok at the security level because this method is private and not callable through XML-RPC.
3867 :param access_rights_uid: optional user ID to use when checking access rights
3868 (not for ir.rules, this is only for ir.model.access)
3872 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
3873 # compute the where, order by, limit and offset clauses
3874 (where_clause, where_clause_params, tables) = self._where_calc(cr, user, args, context=context)
3876 # apply direct ir.rules from current model
3877 self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', context=context)
3879 # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
3880 for inherited_model in self._inherits:
3881 previous_tables = list(tables)
3882 if self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
3883 # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
3884 # list of table in case the inherited table was not in the list before (as that means the corresponding
3885 # JOIN(s) was(were) not present)
3886 self._inherits_join_add(inherited_model, previous_tables, where_clause)
3887 tables = list(set(tables).union(set(previous_tables)))
3889 where = where_clause
3891 order_by = self._order
3893 self._check_qorder(order)
3894 o = order.split(' ')[0]
3895 if (o in self._columns):
3896 # we can only do efficient sort if the fields is stored in database
3897 if getattr(self._columns[o], '_classic_read'):
3899 elif (o in self._inherit_fields):
3900 parent_obj = self.pool.get(self._inherit_fields[o][0])
3901 if getattr(parent_obj._columns[o], '_classic_read'):
3902 # Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
3903 inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
3905 limit_str = limit and ' limit %d' % limit or ''
3906 offset_str = offset and ' offset %d' % offset or ''
3909 where_str = " WHERE %s" % " AND ".join(where)
3914 cr.execute('select count(%s.id) from ' % self._table +
3915 ','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
3918 cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params)
3920 return [x[0] for x in res]
3922 # returns the different values ever entered for one field
3923 # this is used, for example, in the client when the user hits enter on
3925 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3928 if field in self._inherit_fields:
3929 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3931 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3933 def copy_data(self, cr, uid, id, default=None, context=None):
3935 Copy given record's data with all its fields values
3937 :param cr: database cursor
3938 :param user: current user id
3939 :param id: id of the record to copy
3940 :param default: field values to override in the original values of the copied record
3941 :type default: dictionary
3942 :param context: context arguments, like lang, time zone
3943 :type context: dictionary
3944 :return: dictionary containing all the field values
3951 if 'state' not in default:
3952 if 'state' in self._defaults:
3953 if callable(self._defaults['state']):
3954 default['state'] = self._defaults['state'](self, cr, uid, context)
3956 default['state'] = self._defaults['state']
3958 context_wo_lang = context
3959 if 'lang' in context:
3960 del context_wo_lang['lang']
3961 data = self.read(cr, uid, [id], context=context_wo_lang)[0]
3963 fields = self.fields_get(cr, uid, context=context)
3965 ftype = fields[f]['type']
3967 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3971 data[f] = default[f]
3972 elif ftype == 'function':
3974 elif ftype == 'many2one':
3976 data[f] = data[f] and data[f][0]
3979 elif ftype in ('one2many', 'one2one'):
3981 rel = self.pool.get(fields[f]['relation'])
3983 # duplicate following the order of the ids
3984 # because we'll rely on it later for copying
3985 # translations in copy_translation()!
3987 for rel_id in data[f]:
3988 # the lines are first duplicated using the wrong (old)
3989 # parent but then are reassigned to the correct one thanks
3990 # to the (0, 0, ...)
3991 d = rel.copy_data(cr, uid, rel_id, context=context)
3992 res.append((0, 0, d))
3994 elif ftype == 'many2many':
3995 data[f] = [(6, 0, data[f])]
3999 # make sure we don't break the current parent_store structure and
4000 # force a clean recompute!
4001 for parent_column in ['parent_left', 'parent_right']:
4002 data.pop(parent_column, None)
4004 for v in self._inherits:
4005 del data[self._inherits[v]]
4008 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4009 trans_obj = self.pool.get('ir.translation')
4010 fields = self.fields_get(cr, uid, context=context)
4012 translation_records = []
4013 for field_name, field_def in fields.items():
4014 # we must recursively copy the translations for o2o and o2m
4015 if field_def['type'] in ('one2one', 'one2many'):
4016 target_obj = self.pool.get(field_def['relation'])
4017 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4018 # here we rely on the order of the ids to match the translations
4019 # as foreseen in copy_data()
4020 old_childs = sorted(old_record[field_name])
4021 new_childs = sorted(new_record[field_name])
4022 for (old_child, new_child) in zip(old_childs, new_childs):
4023 # recursive copy of translations here
4024 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4025 # and for translatable fields we keep them for copy
4026 elif field_def.get('translate'):
4028 if field_name in self._columns:
4029 trans_name = self._name + "," + field_name
4030 elif field_name in self._inherit_fields:
4031 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4033 trans_ids = trans_obj.search(cr, uid, [
4034 ('name', '=', trans_name),
4035 ('res_id','=', old_id)
4037 translation_records.extend(trans_obj.read(cr,uid,trans_ids,context=context))
4039 for record in translation_records:
4041 record['res_id'] = new_id
4042 trans_obj.create(cr, uid, record, context=context)
4045 def copy(self, cr, uid, id, default=None, context=None):
4047 Duplicate record with given id updating it with default values
4049 :param cr: database cursor
4050 :param uid: current user id
4051 :param id: id of the record to copy
4052 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4053 :type default: dictionary
4054 :param context: context arguments, like lang, time zone
4055 :type context: dictionary
4059 data = self.copy_data(cr, uid, id, default, context)
4060 new_id = self.create(cr, uid, data, context)
4061 self.copy_translations(cr, uid, id, new_id, context)
4064 def exists(self, cr, uid, ids, context=None):
4065 if type(ids) in (int,long):
4067 query = 'SELECT count(1) FROM "%s"' % (self._table)
4068 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4069 return cr.fetchone()[0] == len(ids)
4071 def check_recursion(self, cr, uid, ids, parent=None):
4073 Verifies that there is no loop in a hierarchical structure of records,
4074 by following the parent relationship using the **parent** field until a loop
4075 is detected or until a top-level record is found.
4077 :param cr: database cursor
4078 :param uid: current user id
4079 :param ids: list of ids of records to check
4080 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4081 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4085 parent = self._parent_name
4087 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4090 for i in range(0, len(ids), cr.IN_MAX):
4091 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4092 cr.execute(query, (tuple(sub_ids_parent),))
4093 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4094 ids_parent = ids_parent2
4095 for i in ids_parent:
4100 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4101 """Find out the XML ID of any database record, if there
4102 is one. This method works as a possible implementation
4103 for a function field, to be able to add it to any
4104 model object easily, referencing it as ``osv.osv.get_xml_id``.
4106 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4108 :return: the fully qualified XML ID of the given object,
4109 defaulting to an empty string when there's none
4110 (to be usable as a function field).
4112 result = dict.fromkeys(ids, '')
4113 model_data_obj = self.pool.get('ir.model.data')
4114 data_ids = model_data_obj.search(cr,uid,
4115 [('model','=',self._name),('res_id','in',ids)])
4116 data_results = model_data_obj.read(cr,uid,data_ids,
4117 ['name','module','res_id'])
4118 for record in data_results:
4119 result[record['res_id']] = '%(module)s.%(name)s' % record
4122 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: