1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 # Object relationnal mapping to postgresql module
24 # . Hierarchical structure
25 # . Constraints consistency, validations
26 # . Object meta Data depends on its status
27 # . Optimised processing by complex query (multiple actions at once)
28 # . Default fields value
29 # . Permissions optimisation
30 # . Persistant object: DB postgresql
32 # . Multi-level caching system
33 # . 2 different inheritancies
35 # - classicals (varchar, integer, boolean, ...)
36 # - relations (one2many, many2one, many2many)
52 from lxml import etree
53 from tools.config import config
54 from tools.translate import _
58 from tools.safe_eval import safe_eval as eval
60 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
63 POSTGRES_CONFDELTYPES = {
71 def last_day_of_current_month():
72 today = datetime.date.today()
73 last_day = str(calendar.monthrange(today.year, today.month)[1])
74 return time.strftime('%Y-%m-' + last_day)
76 def intersect(la, lb):
77 return filter(lambda x: x in lb, la)
79 class except_orm(Exception):
80 def __init__(self, name, value):
83 self.args = (name, value)
85 class BrowseRecordError(Exception):
88 # Readonly python database object browser
89 class browse_null(object):
94 def __getitem__(self, name):
97 def __getattr__(self, name):
98 return None # XXX: return self ?
106 def __nonzero__(self):
109 def __unicode__(self):
114 # TODO: execute an object method on browse_record_list
116 class browse_record_list(list):
118 def __init__(self, lst, context=None):
121 super(browse_record_list, self).__init__(lst)
122 self.context = context
125 class browse_record(object):
126 logger = netsvc.Logger()
128 def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}):
130 table : the object (inherited from orm)
131 context : dictionary with an optional context
135 self._list_class = list_class or browse_record_list
140 self._table_name = self._table._name
141 self.__logger = logging.getLogger(
142 'osv.browse_record.' + self._table_name)
143 self._context = context
144 self._fields_process = fields_process
146 cache.setdefault(table._name, {})
147 self._data = cache[table._name]
149 if not (id and isinstance(id, (int, long,))):
150 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
151 # if not table.exists(cr, uid, id, context):
152 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
154 if id not in self._data:
155 self._data[id] = {'id': id}
159 def __getitem__(self, name):
163 if name not in self._data[self._id]:
164 # build the list of fields we will fetch
166 # fetch the definition of the field which was asked for
167 if name in self._table._columns:
168 col = self._table._columns[name]
169 elif name in self._table._inherit_fields:
170 col = self._table._inherit_fields[name][2]
171 elif hasattr(self._table, str(name)):
172 attr = getattr(self._table, name)
174 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
175 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
179 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
180 "Field '%s' does not exist in object '%s': \n%s" % (
181 name, self, ''.join(traceback.format_exc())))
182 raise KeyError("Field '%s' does not exist in object '%s'" % (
185 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
187 # gen the list of "local" (ie not inherited) fields which are classic or many2one
188 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
189 # gen the list of inherited fields
190 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
191 # complete the field list with the inherited fields which are classic or many2one
192 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
193 # otherwise we fetch only that field
195 fields_to_fetch = [(name, col)]
196 ids = filter(lambda id: name not in self._data[id], self._data.keys())
198 field_names = map(lambda x: x[0], fields_to_fetch)
199 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
200 if self._fields_process:
201 lang = self._context.get('lang', 'en_US') or 'en_US'
202 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
204 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
205 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
207 for field_name, field_column in fields_to_fetch:
208 if field_column._type in self._fields_process:
209 for result_line in field_values:
210 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
211 if result_line[field_name]:
212 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
215 # Where did those ids come from? Perhaps old entries in ir_model_dat?
216 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
217 raise KeyError('Field %s not found in %s'%(name,self))
218 # create browse records for 'remote' objects
219 for result_line in field_values:
221 for field_name, field_column in fields_to_fetch:
222 if field_column._type in ('many2one', 'one2one'):
223 if result_line[field_name]:
224 obj = self._table.pool.get(field_column._obj)
225 if isinstance(result_line[field_name], (list,tuple)):
226 value = result_line[field_name][0]
228 value = result_line[field_name]
230 # FIXME: this happen when a _inherits object
231 # overwrite a field of it parent. Need
232 # testing to be sure we got the right
233 # object and not the parent one.
234 if not isinstance(value, browse_record):
235 new_data[field_name] = browse_record(self._cr,
236 self._uid, value, obj, self._cache,
237 context=self._context,
238 list_class=self._list_class,
239 fields_process=self._fields_process)
241 new_data[field_name] = value
243 new_data[field_name] = browse_null()
245 new_data[field_name] = browse_null()
246 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
247 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)
248 elif field_column._type in ('reference'):
249 if result_line[field_name]:
250 if isinstance(result_line[field_name], browse_record):
251 new_data[field_name] = result_line[field_name]
253 ref_obj, ref_id = result_line[field_name].split(',')
254 ref_id = long(ref_id)
255 obj = self._table.pool.get(ref_obj)
256 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)
258 new_data[field_name] = browse_null()
260 new_data[field_name] = result_line[field_name]
261 self._data[result_line['id']].update(new_data)
263 if not name in self._data[self._id]:
264 #how did this happen?
265 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
266 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
267 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
268 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
269 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
270 return self._data[self._id][name]
272 def __getattr__(self, name):
276 raise AttributeError(e)
278 def __contains__(self, name):
279 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
281 def __hasattr__(self, name):
288 return "browse_record(%s, %d)" % (self._table_name, self._id)
290 def __eq__(self, other):
291 if not isinstance(other, browse_record):
293 return (self._table_name, self._id) == (other._table_name, other._id)
295 def __ne__(self, other):
296 if not isinstance(other, browse_record):
298 return (self._table_name, self._id) != (other._table_name, other._id)
300 # we need to define __unicode__ even though we've already defined __str__
301 # because we have overridden __getattr__
302 def __unicode__(self):
303 return unicode(str(self))
306 return hash((self._table_name, self._id))
314 (type returned by postgres when the column was created, type expression to create the column)
318 fields.boolean: 'bool',
319 fields.integer: 'int4',
320 fields.integer_big: 'int8',
324 fields.datetime: 'timestamp',
325 fields.binary: 'bytea',
326 fields.many2one: 'int4',
328 if type(f) in type_dict:
329 f_type = (type_dict[type(f)], type_dict[type(f)])
330 elif isinstance(f, fields.float):
332 f_type = ('numeric', 'NUMERIC')
334 f_type = ('float8', 'DOUBLE PRECISION')
335 elif isinstance(f, (fields.char, fields.reference)):
336 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
337 elif isinstance(f, fields.selection):
338 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
339 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
340 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
343 f_size = getattr(f, 'size', None) or 16
346 f_type = ('int4', 'INTEGER')
348 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
349 elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict:
350 t = eval('fields.'+(f._type), globals())
351 f_type = (type_dict[t], type_dict[t])
352 elif isinstance(f, fields.function) and f._type == 'float':
354 f_type = ('numeric', 'NUMERIC')
356 f_type = ('float8', 'DOUBLE PRECISION')
357 elif isinstance(f, fields.function) and f._type == 'selection':
358 f_type = ('text', 'text')
359 elif isinstance(f, fields.function) and f._type == 'char':
360 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
362 logger = netsvc.Logger()
363 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
368 class orm_template(object):
374 _parent_name = 'parent_id'
375 _parent_store = False
376 _parent_order = False
386 CONCURRENCY_CHECK_FIELD = '__last_update'
387 def log(self, cr, uid, id, message, secondary=False, context=None):
388 return self.pool.get('res.log').create(cr, uid, {
390 'res_model': self._name,
391 'secondary': secondary,
396 def view_init(self, cr , uid , fields_list, context=None):
397 """Override this method to do specific things when a view on the object is opened."""
400 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
401 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
403 def _field_create(self, cr, context={}):
404 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
406 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
407 model_id = cr.fetchone()[0]
408 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'))
410 model_id = cr.fetchone()[0]
411 if 'module' in context:
412 name_id = 'model_'+self._name.replace('.','_')
413 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module']))
415 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
416 (name_id, context['module'], 'ir.model', model_id)
421 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
423 for rec in cr.dictfetchall():
424 cols[rec['name']] = rec
426 for (k, f) in self._columns.items():
428 'model_id': model_id,
431 'field_description': f.string.replace("'", " "),
433 'relation': f._obj or '',
434 'view_load': (f.view_load and 1) or 0,
435 'select_level': tools.ustr(f.select or 0),
436 'readonly':(f.readonly and 1) or 0,
437 'required':(f.required and 1) or 0,
438 'selectable' : (f.selectable and 1) or 0,
439 'relation_field': (f._type=='one2many' and isinstance(f,fields.one2many)) and f._fields_id or '',
441 # When its a custom field,it does not contain f.select
442 if context.get('field_state','base') == 'manual':
443 if context.get('field_name','') == k:
444 vals['select_level'] = context.get('select','0')
445 #setting value to let the problem NOT occur next time
447 vals['select_level'] = cols[k]['select_level']
450 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
451 id = cr.fetchone()[0]
453 cr.execute("""INSERT INTO ir_model_fields (
454 id, model_id, model, name, field_description, ttype,
455 relation,view_load,state,select_level,relation_field
457 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
459 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
460 vals['relation'], bool(vals['view_load']), 'base',
461 vals['select_level'],vals['relation_field']
463 if 'module' in context:
464 name1 = 'field_' + self._table + '_' + k
465 cr.execute("select name from ir_model_data where name=%s", (name1,))
467 name1 = name1 + "_" + str(id)
468 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
469 (name1, context['module'], 'ir.model.fields', id)
472 for key, val in vals.items():
473 if cols[k][key] != vals[key]:
474 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
476 cr.execute("""UPDATE ir_model_fields SET
477 model_id=%s, field_description=%s, ttype=%s, relation=%s,
478 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
480 model=%s AND name=%s""", (
481 vals['model_id'], vals['field_description'], vals['ttype'],
482 vals['relation'], bool(vals['view_load']),
483 vals['select_level'], bool(vals['readonly']),bool(vals['required']),bool(vals['selectable']),vals['relation_field'],vals['model'], vals['name']
488 def _auto_init(self, cr, context={}):
489 self._field_create(cr, context)
491 def __init__(self, cr):
492 if not self._name and not hasattr(self, '_inherit'):
493 name = type(self).__name__.split('.')[0]
494 msg = "The class %s has to have a _name attribute" % name
496 logger = netsvc.Logger()
497 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
498 raise except_orm('ValueError', msg )
500 if not self._description:
501 self._description = self._name
503 self._table = self._name.replace('.', '_')
505 def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
507 Fetch records as objects allowing to use dot notation to browse fields and relations
509 :param cr: database cursor
510 :param user: current user id
511 :param select: id or list of ids
512 :param context: context arguments, like lang, time zone
513 :rtype: object or list of objects requested
518 self._list_class = list_class or browse_record_list
520 # need to accepts ints and longs because ids coming from a method
521 # launched by button in the interface have a type long...
522 if isinstance(select, (int, long)):
523 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
524 elif isinstance(select, list):
525 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)
529 def __export_row(self, cr, uid, row, fields, context=None):
533 def check_type(field_type):
534 if field_type == 'float':
536 elif field_type == 'integer':
538 elif field_type == 'boolean':
542 def selection_field(in_field):
543 col_obj = self.pool.get(in_field.keys()[0])
544 if f[i] in col_obj._columns.keys():
545 return col_obj._columns[f[i]]
546 elif f[i] in col_obj._inherits.keys():
547 selection_field(col_obj._inherits)
553 data = map(lambda x: '', range(len(fields)))
555 for fpos in range(len(fields)):
564 model_data = self.pool.get('ir.model.data')
565 data_ids = model_data.search(cr, uid, [('model','=',r._table_name),('res_id','=',r['id'])])
567 d = model_data.read(cr, uid, data_ids, ['name','module'])[0]
569 r = '%s.%s'%(d['module'],d['name'])
576 # To display external name of selection field when its exported
577 if not context.get('import_comp',False):# Allow external name only if its not import compatible
579 if f[i] in self._columns.keys():
580 cols = self._columns[f[i]]
581 elif f[i] in self._inherit_fields.keys():
582 cols = selection_field(self._inherits)
583 if cols and cols._type == 'selection':
584 sel_list = cols.selection
585 if r and type(sel_list) == type([]):
586 r = [x[1] for x in sel_list if r==x[0]]
587 r = r and r[0] or False
589 if f[i] in self._columns:
590 r = check_type(self._columns[f[i]]._type)
591 elif f[i] in self._inherit_fields:
592 r = check_type(self._inherit_fields[f[i]][2]._type)
595 if isinstance(r, (browse_record_list, list)):
597 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
603 lines2 = self.__export_row(cr, uid, row2, fields2,
606 for fpos2 in range(len(fields)):
607 if lines2 and lines2[0][fpos2]:
608 data[fpos2] = lines2[0][fpos2]
612 if isinstance(rr.name, browse_record):
614 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
615 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
616 dt += tools.ustr(rr_name or '') + ','
626 if isinstance(r, browse_record):
627 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
628 r = r and r[0] and r[0][1] or ''
629 data[fpos] = tools.ustr(r or '')
630 return [data] + lines
632 def export_data(self, cr, uid, ids, fields_to_export, context=None):
634 Export fields for selected objects
636 :param cr: database cursor
637 :param uid: current user id
638 :param ids: list of ids
639 :param fields_to_export: list of fields
640 :param context: context arguments, like lang, time zone, may contain import_comp(default: False) to make exported data compatible with import_data()
641 :rtype: dictionary with a *datas* matrix
643 This method is used when exporting data via client menu
648 imp_comp = context.get('import_comp',False)
649 cols = self._columns.copy()
650 for f in self._inherit_fields:
651 cols.update({f: self._inherit_fields[f][2]})
652 fields_to_export = map(lambda x: x.split('/'), fields_to_export)
653 fields_export = fields_to_export+[]
656 for field in fields_export:
657 if imp_comp and len(field)>1:
658 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
659 elif len (field) <=1:
660 if imp_comp and cols.get(field and field[0],False):
661 if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
662 or isinstance(cols[field[0]], fields.related)\
663 or isinstance(cols[field[0]], fields.one2many)):
664 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
666 if imp_comp and len(warning_fields):
667 warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' %('\n'.join(warning_fields))
669 return {'warning' : warning}
670 for row in self.browse(cr, uid, ids, context):
671 datas += self.__export_row(cr, uid, row, fields_to_export, context)
672 return {'datas':datas}
674 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
676 Import given data in given module
678 :param cr: database cursor
679 :param uid: current user id
680 :param ids: list of ids
681 :param fields: list of fields
682 :param data: data to import
683 :param mode: 'init' or 'update' for record creation
684 :param current_module: module name
685 :param noupdate: flag for record creation
686 :param context: context arguments, like lang, time zone,
687 :param filename: optional file to store partial import state for recovery
690 This method is used when importing data via client menu
695 fields = map(lambda x: x.split('/'), fields)
696 logger = netsvc.Logger()
697 ir_model_data_obj = self.pool.get('ir.model.data')
699 def _check_db_id(self, model_name, db_id):
700 obj_model = self.pool.get(model_name)
701 ids = obj_model.search(cr, uid, [('id','=',int(db_id))])
703 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
706 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
707 line = datas[position]
716 ir_model_data_obj = self.pool.get('ir.model.data')
718 # Import normal fields
720 for i in range(len(fields)):
722 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
727 if prefix and not prefix[0] in field:
730 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
734 field_name = field[0].split(':')[0]
735 model_rel = fields_def[field_name]['relation']
737 if fields_def[field[len(prefix)][:-6]]['type']=='many2many':
739 for db_id in line[i].split(config.get('csv_internal_sep')):
741 _check_db_id(self, model_rel, db_id)
744 warning += [tools.exception_to_unicode(e)]
745 logger.notifyChannel("import", netsvc.LOG_ERROR,
746 tools.exception_to_unicode(e))
748 res = [(6, 0, res_id)]
751 _check_db_id(self, model_rel, line[i])
754 warning += [tools.exception_to_unicode(e)]
755 logger.notifyChannel("import", netsvc.LOG_ERROR,
756 tools.exception_to_unicode(e))
757 row[field_name] = res or False
760 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
763 if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
765 for word in line[i].split(config.get('csv_internal_sep')):
767 module, xml_id = word.rsplit('.', 1)
769 module, xml_id = current_module, word
770 id = ir_model_data_obj._get_id(cr, uid, module,
772 res_id2 = ir_model_data_obj.read(cr, uid, [id],
773 ['res_id'])[0]['res_id']
775 res_id.append(res_id2)
777 res_id = [(6, 0, res_id)]
780 module, xml_id = line[i].rsplit('.', 1)
782 module, xml_id = current_module, line[i]
783 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
784 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
786 res_id = ir_model_data[0]['res_id']
788 raise ValueError('No references to %s.%s' % (module, xml_id))
789 row[field[-1][:-3]] = res_id or False
791 if (len(field) == len(prefix)+1) and \
792 len(field[len(prefix)].split(':lang=')) == 2:
793 f, lang = field[len(prefix)].split(':lang=')
794 translate.setdefault(lang, {})[f]=line[i] or False
796 if (len(field) == len(prefix)+1) and \
797 (prefix == field[0:len(prefix)]):
798 if field[len(prefix)] == "id":
801 is_xml_id = data_id = line[i]
802 d = data_id.split('.')
803 module = len(d)>1 and d[0] or ''
804 name = len(d)>1 and d[1] or d[0]
805 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('name','=',name)])
807 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
809 if is_db_id and not db_id:
810 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('res_id','=',is_db_id)])
811 if not len(data_ids):
812 ir_model_data_obj.create(cr, uid, {'module':module, 'model':model_name, 'name':name, 'res_id':is_db_id})
814 if is_db_id and int(db_id) != int(is_db_id):
815 warning += [_("Id is not the same than existing one: %s")%(is_db_id)]
816 logger.notifyChannel("import", netsvc.LOG_ERROR,
817 _("Id is not the same than existing one: %s")%(is_db_id))
820 if field[len(prefix)] == "db_id":
823 _check_db_id(self, model_name, line[i])
824 data_res_id = is_db_id = int(line[i])
826 warning += [tools.exception_to_unicode(e)]
827 logger.notifyChannel("import", netsvc.LOG_ERROR,
828 tools.exception_to_unicode(e))
830 data_ids = ir_model_data_obj.search(cr, uid, [('model','=',model_name),('res_id','=',line[i])])
832 d = ir_model_data_obj.read(cr, uid, data_ids, ['name','module'])[0]
835 data_id = '%s.%s'%(d['module'],d['name'])
838 if is_xml_id and not data_id:
840 if is_xml_id and is_xml_id!=data_id:
841 warning += [_("Id is not the same than existing one: %s")%(line[i])]
842 logger.notifyChannel("import", netsvc.LOG_ERROR,
843 _("Id is not the same than existing one: %s")%(line[i]))
846 if fields_def[field[len(prefix)]]['type'] == 'integer':
847 res = line[i] and int(line[i])
848 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
849 res = line[i].lower() not in ('0', 'false', 'off')
850 elif fields_def[field[len(prefix)]]['type'] == 'float':
851 res = line[i] and float(line[i])
852 elif fields_def[field[len(prefix)]]['type'] == 'selection':
854 if isinstance(fields_def[field[len(prefix)]]['selection'],
856 sel = fields_def[field[len(prefix)]]['selection']
858 sel = fields_def[field[len(prefix)]]['selection'](self,
861 if line[i] in [tools.ustr(key),tools.ustr(val)]: #Acepting key or value for selection field
864 if line[i] and not res:
865 logger.notifyChannel("import", netsvc.LOG_WARNING,
866 _("key '%s' not found in selection field '%s'") % \
867 (line[i], field[len(prefix)]))
869 warning += [_("Key/value '%s' not found in selection field '%s'")%(line[i],field[len(prefix)])]
871 elif fields_def[field[len(prefix)]]['type']=='many2one':
874 relation = fields_def[field[len(prefix)]]['relation']
875 res2 = self.pool.get(relation).name_search(cr, uid,
876 line[i], [], operator='=', context=context)
877 res = (res2 and res2[0][0]) or False
879 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
880 logger.notifyChannel("import", netsvc.LOG_WARNING,
881 _("Relation not found: %s on '%s'")%(line[i],relation))
882 elif fields_def[field[len(prefix)]]['type']=='many2many':
885 relation = fields_def[field[len(prefix)]]['relation']
886 for word in line[i].split(config.get('csv_internal_sep')):
887 res2 = self.pool.get(relation).name_search(cr,
888 uid, word, [], operator='=', context=context)
889 res3 = (res2 and res2[0][0]) or False
891 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
892 logger.notifyChannel("import",
894 _("Relation not found: %s on '%s'")%(line[i],relation))
900 res = line[i] or False
901 row[field[len(prefix)]] = res
902 elif (prefix==field[0:len(prefix)]):
903 if field[0] not in todo:
904 todo.append(field[len(prefix)])
906 # Import one2many, many2many fields
910 relation_obj = self.pool.get(fields_def[field]['relation'])
911 newfd = relation_obj.fields_get(
912 cr, uid, context=context)
913 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
914 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
915 nbrmax = max(nbrmax, max2)
916 warning = warning + w2
917 reduce(lambda x, y: x and y, newrow)
918 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
919 [(0, 0, newrow)]) or []
921 while (position+i)<len(datas):
923 for j in range(len(fields)):
925 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
930 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
931 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
933 if reduce(lambda x, y: x or y, newrow.values()):
934 row[field].append((0, 0, newrow))
936 nbrmax = max(nbrmax, i)
939 for i in range(max(nbrmax, 1)):
942 result = (row, nbrmax, warning, translate, data_id, data_res_id)
945 fields_def = self.fields_get(cr, uid, context=context)
948 initial_size = len(datas)
949 if config.get('import_partial', False) and filename:
950 data = pickle.load(file(config.get('import_partial')))
951 original_value = data.get(filename, 0)
957 (res, other, warning, translate, data_id, res_id) = \
958 process_liness(self, datas, [], current_module, self._name, fields_def)
961 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
964 id = ir_model_data_obj._update(cr, uid, self._name,
965 current_module, res, xml_id=data_id, mode=mode,
966 noupdate=noupdate, res_id=res_id, context=context)
971 if isinstance(e,psycopg2.IntegrityError):
972 msg= _('Insertion Failed! ')
973 for key in self.pool._sql_error.keys():
975 msg = self.pool._sql_error[key]
977 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
978 if isinstance(e, osv.orm.except_orm ):
979 msg = _('Insertion Failed! ' + e[1])
980 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
981 #Raising Uncaught exception
982 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '' )
984 for lang in translate:
985 context2 = context.copy()
986 context2['lang'] = lang
987 self.write(cr, uid, [id], translate[lang], context2)
988 if config.get('import_partial', False) and filename and (not (counter%100)) :
989 data = pickle.load(file(config.get('import_partial')))
990 data[filename] = initial_size - len(datas) + original_value
991 pickle.dump(data, file(config.get('import_partial'),'wb'))
992 if context.get('defer_parent_store_computation'):
993 self._parent_store_compute(cr)
996 #except Exception, e:
997 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1000 # return (-1, res, e[0], warning)
1002 # return (-1, res, e[0], '')
1005 # TODO: Send a request with the result and multi-thread !
1007 if context.get('defer_parent_store_computation'):
1008 self._parent_store_compute(cr)
1009 return (done, 0, 0, 0)
1011 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1012 raise NotImplementedError(_('The read method is not implemented on this object !'))
1014 def get_invalid_fields(self,cr,uid):
1015 return list(self._invalids)
1017 def _validate(self, cr, uid, ids, context=None):
1018 context = context or {}
1019 lng = context.get('lang', False) or 'en_US'
1020 trans = self.pool.get('ir.translation')
1022 for constraint in self._constraints:
1023 fun, msg, fields = constraint
1024 if not fun(self, cr, uid, ids):
1025 # Check presence of __call__ directly instead of using
1026 # callable() because it will be deprecated as of Python 3.0
1027 if hasattr(msg, '__call__'):
1028 txt_msg, params = msg(self, cr, uid, ids)
1029 tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg
1030 translated_msg = tmp_msg % params
1032 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1034 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1036 self._invalids.update(fields)
1039 raise except_orm('ValidateError', '\n'.join(error_msgs))
1041 self._invalids.clear()
1043 def default_get(self, cr, uid, fields_list, context=None):
1045 Returns default values for the fields in fields_list.
1047 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1048 :type fields_list: list
1049 :param context: usual context dictionary - it may contains keys in the form ``default_XXX``,
1050 where XXX is a field name to set or override a default value.
1051 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1053 # trigger view init hook
1054 self.view_init(cr, uid, fields_list, context)
1060 # get the default values for the inherited fields
1061 for t in self._inherits.keys():
1062 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1065 # get the default values defined in the object
1066 for f in fields_list:
1067 if f in self._defaults:
1068 if callable(self._defaults[f]):
1069 defaults[f] = self._defaults[f](self, cr, uid, context)
1071 defaults[f] = self._defaults[f]
1073 fld_def = ((f in self._columns) and self._columns[f]) \
1074 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1077 if isinstance(fld_def, fields.property):
1078 property_obj = self.pool.get('ir.property')
1079 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1081 if isinstance(prop_value, (browse_record, browse_null)):
1082 defaults[f] = prop_value.id
1084 defaults[f] = prop_value
1086 if f not in defaults:
1089 # get the default values set by the user and override the default
1090 # values defined in the object
1091 ir_values_obj = self.pool.get('ir.values')
1092 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1093 for id, field, field_value in res:
1094 if field in fields_list:
1095 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1096 if fld_def._type in ('many2one', 'one2one'):
1097 obj = self.pool.get(fld_def._obj)
1098 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1100 if fld_def._type in ('many2many'):
1101 obj = self.pool.get(fld_def._obj)
1103 for i in range(len(field_value)):
1104 if not obj.search(cr, uid, [('id', '=',
1107 field_value2.append(field_value[i])
1108 field_value = field_value2
1109 if fld_def._type in ('one2many'):
1110 obj = self.pool.get(fld_def._obj)
1112 for i in range(len(field_value)):
1113 field_value2.append({})
1114 for field2 in field_value[i]:
1115 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1116 obj2 = self.pool.get(obj._columns[field2]._obj)
1117 if not obj2.search(cr, uid,
1118 [('id', '=', field_value[i][field2])]):
1120 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1121 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1122 if not obj2.search(cr, uid,
1123 [('id', '=', field_value[i][field2])]):
1125 # TODO add test for many2many and one2many
1126 field_value2[i][field2] = field_value[i][field2]
1127 field_value = field_value2
1128 defaults[field] = field_value
1130 # get the default values from the context
1131 for key in context or {}:
1132 if key.startswith('default_') and (key[8:] in fields_list):
1133 defaults[key[8:]] = context[key]
1137 def perm_read(self, cr, user, ids, context=None, details=True):
1138 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1140 def unlink(self, cr, uid, ids, context=None):
1141 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1143 def write(self, cr, user, ids, vals, context=None):
1144 raise NotImplementedError(_('The write method is not implemented on this object !'))
1146 def create(self, cr, user, vals, context=None):
1147 raise NotImplementedError(_('The create method is not implemented on this object !'))
1149 def fields_get_keys(self, cr, user, context=None):
1150 res = self._columns.keys()
1151 for parent in self._inherits:
1152 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1155 # returns the definition of each field in the object
1156 # the optional fields parameter can limit the result to some fields
1157 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1161 translation_obj = self.pool.get('ir.translation')
1162 for parent in self._inherits:
1163 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1165 if self._columns.keys():
1166 for f in self._columns.keys():
1167 if allfields and f not in allfields:
1169 res[f] = {'type': self._columns[f]._type}
1170 # This additional attributes for M2M and function field is added
1171 # because we need to display tooltip with this additional information
1172 # when client is started in debug mode.
1173 if isinstance(self._columns[f], fields.function):
1174 res[f]['function'] = self._columns[f]._fnct and self._columns[f]._fnct.func_name or False
1175 res[f]['store'] = self._columns[f].store
1176 if isinstance(self._columns[f].store, dict):
1177 res[f]['store'] = str(self._columns[f].store)
1178 res[f]['fnct_search'] = self._columns[f]._fnct_search and self._columns[f]._fnct_search.func_name or False
1179 res[f]['fnct_inv'] = self._columns[f]._fnct_inv and self._columns[f]._fnct_inv.func_name or False
1180 res[f]['fnct_inv_arg'] = self._columns[f]._fnct_inv_arg or False
1181 res[f]['func_obj'] = self._columns[f]._obj or False
1182 res[f]['func_method'] = self._columns[f]._method
1183 if isinstance(self._columns[f], fields.many2many):
1184 res[f]['related_columns'] = list((self._columns[f]._id1, self._columns[f]._id2))
1185 res[f]['third_table'] = self._columns[f]._rel
1186 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1187 'change_default', 'translate', 'help', 'select', 'selectable'):
1188 if getattr(self._columns[f], arg):
1189 res[f][arg] = getattr(self._columns[f], arg)
1190 if not write_access:
1191 res[f]['readonly'] = True
1192 res[f]['states'] = {}
1193 for arg in ('digits', 'invisible','filters'):
1194 if getattr(self._columns[f], arg, None):
1195 res[f][arg] = getattr(self._columns[f], arg)
1197 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', self._columns[f].string)
1199 res[f]['string'] = res_trans
1200 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1202 res[f]['help'] = help_trans
1204 if hasattr(self._columns[f], 'selection'):
1205 if isinstance(self._columns[f].selection, (tuple, list)):
1206 sel = self._columns[f].selection
1207 # translate each selection option
1209 for (key, val) in sel:
1212 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1213 sel2.append((key, val2 or val))
1215 res[f]['selection'] = sel
1217 # call the 'dynamic selection' function
1218 res[f]['selection'] = self._columns[f].selection(self, cr,
1220 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1221 res[f]['relation'] = self._columns[f]._obj
1222 res[f]['domain'] = self._columns[f]._domain
1223 res[f]['context'] = self._columns[f]._context
1225 #TODO : read the fields from the database
1229 # filter out fields which aren't in the fields list
1230 for r in res.keys():
1231 if r not in allfields:
1236 # Overload this method if you need a window title which depends on the context
1238 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1241 def __view_look_dom(self, cr, user, node, view_id, context=None):
1249 if isinstance(s, unicode):
1250 return s.encode('utf8')
1253 # return True if node can be displayed to current user
1254 def check_group(node):
1255 if node.get('groups'):
1256 groups = node.get('groups').split(',')
1257 access_pool = self.pool.get('ir.model.access')
1258 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1260 node.set('invisible', '1')
1261 if 'attrs' in node.attrib:
1262 del(node.attrib['attrs']) #avoid making field visible later
1263 del(node.attrib['groups'])
1268 if node.tag in ('field', 'node', 'arrow'):
1269 if node.get('object'):
1274 if f.tag in ('field'):
1275 xml += etree.tostring(f, encoding="utf-8")
1277 new_xml = etree.fromstring(encode(xml))
1278 ctx = context.copy()
1279 ctx['base_model_name'] = self._name
1280 xarch, xfields = self.pool.get(node.get('object',False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1281 views[str(f.tag)] = {
1285 attrs = {'views': views}
1287 fields = views.get('field',False) and views['field'].get('fields',False)
1288 if node.get('name'):
1291 if node.get('name') in self._columns:
1292 column = self._columns[node.get('name')]
1294 column = self._inherit_fields[node.get('name')][2]
1299 relation = self.pool.get(column._obj)
1304 if f.tag in ('form', 'tree', 'graph'):
1306 ctx = context.copy()
1307 ctx['base_model_name'] = self._name
1308 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1309 views[str(f.tag)] = {
1313 attrs = {'views': views}
1314 if node.get('widget') and node.get('widget') == 'selection':
1315 # Prepare the cached selection list for the client. This needs to be
1316 # done even when the field is invisible to the current user, because
1317 # other events could need to change its value to any of the selectable ones
1318 # (such as on_change events, refreshes, etc.)
1320 # If domain and context are strings, we keep them for client-side, otherwise
1321 # we evaluate them server-side to consider them when generating the list of
1323 # TODO: find a way to remove this hack, by allow dynamic domains
1325 if column._domain and not isinstance(column._domain, basestring):
1326 dom = column._domain
1327 dom += eval(node.get('domain','[]'), {'uid':user, 'time':time})
1328 search_context = dict(context)
1329 if column._context and not isinstance(column._context, basestring):
1330 search_context.update(column._context)
1331 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1332 if (node.get('required') and not int(node.get('required'))) or not column.required:
1333 attrs['selection'].append((False,''))
1334 fields[node.get('name')] = attrs
1336 elif node.tag in ('form', 'tree'):
1337 result = self.view_header_get(cr, user, False, node.tag, context)
1339 node.set('string', result)
1341 elif node.tag == 'calendar':
1342 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1343 if node.get(additional_field):
1344 fields[node.get(additional_field)] = {}
1346 if 'groups' in node.attrib:
1350 if ('lang' in context) and not result:
1351 if node.get('string'):
1352 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string').encode('utf8'))
1353 if not trans and ('base_model_name' in context):
1354 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string').encode('utf8'))
1356 node.set('string', trans)
1358 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum').encode('utf8'))
1360 node.set('sum', trans)
1364 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1368 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1369 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1371 rolesobj = self.pool.get('res.roles')
1372 usersobj = self.pool.get('res.users')
1374 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1375 for button in buttons:
1377 if user != 1: # admin user has all roles
1378 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
1379 # TODO handle the case of more than one workflow for a model
1380 cr.execute("""SELECT DISTINCT t.role_id
1382 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1383 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1386 """, (self._name, button.get('name'),))
1387 roles = cr.fetchall()
1389 # draft -> valid = signal_next (role X)
1390 # draft -> cancel = signal_cancel (no role)
1392 # valid -> running = signal_next (role Y)
1393 # valid -> cancel = signal_cancel (role Z)
1395 # running -> done = signal_next (role Z)
1396 # running -> cancel = signal_cancel (role Z)
1398 # As we don't know the object state, in this scenario,
1399 # the button "signal_cancel" will be always shown as there is no restriction to cancel in draft
1400 # the button "signal_next" will be show if the user has any of the roles (X Y or Z)
1401 # The verification will be made later in workflow process...
1403 can_click = any((not role) or rolesobj.check(cr, user, user_roles, role) for (role,) in roles)
1405 button.set('readonly', str(int(not can_click)))
1407 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1410 if node.tag=='diagram':
1411 if node.getchildren()[0].tag=='node':
1412 node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1413 if node.getchildren()[1].tag=='arrow':
1414 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1415 for key,value in node_fields.items():
1417 for key,value in arrow_fields.items():
1420 fields = self.fields_get(cr, user, fields_def.keys(), context)
1421 for field in fields_def:
1423 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1424 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1425 elif field in fields:
1426 fields[field].update(fields_def[field])
1428 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))
1429 res = cr.fetchall()[:]
1431 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1432 msg = "\n * ".join([r[0] for r in res])
1433 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1434 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1435 raise except_orm('View error', msg)
1438 def __get_default_calendar_view(self):
1439 """Generate a default calendar view (For internal use only).
1442 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1443 '<calendar string="%s"') % (self._description)
1445 if (self._date_name not in self._columns):
1447 for dt in ['date','date_start','x_date','x_date_start']:
1448 if dt in self._columns:
1449 self._date_name = dt
1454 raise except_orm(_('Invalid Object Architecture!'),_("Insufficient fields for Calendar View!"))
1457 arch +=' date_start="%s"' % (self._date_name)
1459 for color in ["user_id","partner_id","x_user_id","x_partner_id"]:
1460 if color in self._columns:
1461 arch += ' color="' + color + '"'
1464 dt_stop_flag = False
1466 for dt_stop in ["date_stop","date_end","x_date_stop","x_date_end"]:
1467 if dt_stop in self._columns:
1468 arch += ' date_stop="' + dt_stop + '"'
1472 if not dt_stop_flag:
1473 for dt_delay in ["date_delay","planned_hours","x_date_delay","x_planned_hours"]:
1474 if dt_delay in self._columns:
1475 arch += ' date_delay="' + dt_delay + '"'
1479 ' <field name="%s"/>\n'
1480 '</calendar>') % (self._rec_name)
1484 def __get_default_search_view(self, cr, uid, context={}):
1487 if isinstance(s, unicode):
1488 return s.encode('utf8')
1491 view = self.fields_view_get(cr, uid, False, 'form', context)
1493 root = etree.fromstring(encode(view['arch']))
1494 res = etree.XML("""<search string="%s"></search>""" % root.get("string", ""))
1495 node = etree.Element("group")
1498 fields = root.xpath("//field[@select=1]")
1499 for field in fields:
1502 return etree.tostring(res, encoding="utf-8").replace('\t', '')
1505 # if view_id, view_type is not required
1507 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1509 Get the detailed composition of the requested view like fields, model, view architecture
1511 :param cr: database cursor
1512 :param user: current user id
1513 :param view_id: id of the view or None
1514 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1515 :param context: context arguments, like lang, time zone
1516 :param toolbar: true to include contextual actions
1517 :param submenu: example (portal_project module)
1518 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1519 :raise AttributeError:
1520 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1521 * if some tag other than 'position' is found in parent view
1522 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1529 if isinstance(s, unicode):
1530 return s.encode('utf8')
1533 def _inherit_apply(src, inherit):
1534 def _find(node, node2):
1535 if node2.tag == 'xpath':
1536 res = node.xpath(node2.get('expr'))
1542 for n in node.getiterator(node2.tag):
1544 for attr in node2.attrib:
1545 if attr == 'position':
1548 if n.get(attr) == node2.get(attr):
1555 # End: _find(node, node2)
1557 doc_dest = etree.fromstring(encode(inherit))
1558 toparse = [ doc_dest ]
1561 node2 = toparse.pop(0)
1562 if node2.tag == 'data':
1563 toparse += [ c for c in doc_dest ]
1565 node = _find(src, node2)
1566 if node is not None:
1568 if node2.get('position'):
1569 pos = node2.get('position')
1570 if pos == 'replace':
1571 parent = node.getparent()
1573 src = copy.deepcopy(node2[0])
1576 node.addprevious(child)
1577 node.getparent().remove(node)
1578 elif pos == 'attributes':
1579 for child in node2.getiterator('attribute'):
1580 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1582 node.set(attribute[0], attribute[1])
1584 del(node.attrib[attribute[0]])
1586 sib = node.getnext()
1590 elif pos == 'after':
1594 sib.addprevious(child)
1595 elif pos == 'before':
1596 node.addprevious(child)
1598 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1601 ' %s="%s"' % (attr, node2.get(attr))
1602 for attr in node2.attrib
1603 if attr != 'position'
1605 tag = "<%s%s>" % (node2.tag, attrs)
1606 raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1608 # End: _inherit_apply(src, inherit)
1610 result = {'type': view_type, 'model': self._name}
1616 view_ref = context.get(view_type + '_view_ref', False)
1617 if view_ref and not view_id:
1619 module, view_ref = view_ref.split('.', 1)
1620 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1621 view_ref_res = cr.fetchone()
1623 view_id = view_ref_res[0]
1626 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1629 query += " AND model=%s"
1630 params += (self._name,)
1631 cr.execute(query, params)
1633 cr.execute('''SELECT
1634 arch,name,field_parent,id,type,inherit_id
1641 ORDER BY priority''', (self._name, view_type))
1642 sql_res = cr.fetchone()
1648 view_id = ok or sql_res[3]
1651 # if a view was found
1653 result['type'] = sql_res[4]
1654 result['view_id'] = sql_res[3]
1655 result['arch'] = sql_res[0]
1657 def _inherit_apply_rec(result, inherit_id):
1658 # get all views which inherit from (ie modify) this view
1659 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1660 sql_inherit = cr.fetchall()
1661 for (inherit, id) in sql_inherit:
1662 result = _inherit_apply(result, inherit)
1663 result = _inherit_apply_rec(result, id)
1666 inherit_result = etree.fromstring(encode(result['arch']))
1667 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1669 result['name'] = sql_res[1]
1670 result['field_parent'] = sql_res[2] or False
1673 # otherwise, build some kind of default view
1674 if view_type == 'form':
1675 res = self.fields_get(cr, user, context=context)
1676 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1677 '<form string="%s">' % (self._description,)
1679 if res[x]['type'] not in ('one2many', 'many2many'):
1680 xml += '<field name="%s"/>' % (x,)
1681 if res[x]['type'] == 'text':
1685 elif view_type == 'tree':
1686 _rec_name = self._rec_name
1687 if _rec_name not in self._columns:
1688 _rec_name = self._columns.keys()[0]
1689 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1690 '<tree string="%s"><field name="%s"/></tree>' \
1691 % (self._description, self._rec_name)
1693 elif view_type == 'calendar':
1694 xml = self.__get_default_calendar_view()
1696 elif view_type == 'search':
1697 xml = self.__get_default_search_view(cr, user, context)
1700 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1701 raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type)
1702 result['arch'] = etree.fromstring(encode(xml))
1703 result['name'] = 'default'
1704 result['field_parent'] = False
1705 result['view_id'] = 0
1707 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1708 result['arch'] = xarch
1709 result['fields'] = xfields
1712 if context and context.get('active_id',False):
1713 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1715 act_id = data_menu.id
1717 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1718 result['submenu'] = getattr(data_action,'menus', False)
1722 for key in ('report_sxw_content', 'report_rml_content',
1723 'report_sxw', 'report_rml',
1724 'report_sxw_content_data', 'report_rml_content_data'):
1728 ir_values_obj = self.pool.get('ir.values')
1729 resprint = ir_values_obj.get(cr, user, 'action',
1730 'client_print_multi', [(self._name, False)], False,
1732 resaction = ir_values_obj.get(cr, user, 'action',
1733 'client_action_multi', [(self._name, False)], False,
1736 resrelate = ir_values_obj.get(cr, user, 'action',
1737 'client_action_relate', [(self._name, False)], False,
1739 resprint = map(clean, resprint)
1740 resaction = map(clean, resaction)
1741 resaction = filter(lambda x: not x.get('multi', False), resaction)
1742 resprint = filter(lambda x: not x.get('multi', False), resprint)
1743 resrelate = map(lambda x: x[2], resrelate)
1745 for x in resprint+resaction+resrelate:
1746 x['string'] = x['name']
1748 result['toolbar'] = {
1750 'action': resaction,
1753 if result['type']=='form' and result['arch'].count("default_focus")>1:
1754 msg = "Form View contain more than one default_focus attribute"
1755 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1756 raise except_orm('View Error !',msg)
1759 _view_look_dom_arch = __view_look_dom_arch
1761 def search_count(self, cr, user, args, context=None):
1764 res = self.search(cr, user, args, context=context, count=True)
1765 if isinstance(res, list):
1769 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1771 Search for records based on a search domain.
1773 :param cr: database cursor
1774 :param user: current user id
1775 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1776 :param offset: optional number of results to skip in the returned values (default: 0)
1777 :param limit: optional max number of records to return (default: **None**)
1778 :param order: optional columns to sort by (default: self._order=id )
1779 :param context: optional context arguments, like lang, time zone
1780 :type context: dictionary
1781 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1782 :return: id or list of ids of records matching the criteria
1783 :rtype: integer or list of integers
1784 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1786 **Expressing a search domain (args)**
1788 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1790 * **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.
1791 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1792 The semantics of most of these operators are obvious.
1793 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1794 according to the semantics of this model (i.e following the relationship field named by
1795 ``self._parent_name``, by default ``parent_id``.
1796 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1798 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1799 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1800 Be very careful about this when you combine them the first time.
1802 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1804 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1806 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::
1808 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1811 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1813 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1815 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1816 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1817 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1819 :param access_rights_uid: optional user ID to use when checking access rights
1820 (not for ir.rules, this is only for ir.model.access)
1822 raise NotImplementedError(_('The search method is not implemented on this object !'))
1824 def name_get(self, cr, user, ids, context=None):
1827 :param cr: database cursor
1828 :param user: current user id
1830 :param ids: list of ids
1831 :param context: context arguments, like lang, time zone
1832 :type context: dictionary
1833 :return: tuples with the text representation of requested objects for to-many relationships
1840 if isinstance(ids, (int, long)):
1842 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1843 [self._rec_name], context, load='_classic_write')]
1845 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1847 Search for records and their display names according to a search domain.
1849 :param cr: database cursor
1850 :param user: current user id
1851 :param name: object name to search
1852 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1853 :param operator: operator for search criterion
1854 :param context: context arguments, like lang, time zone
1855 :type context: dictionary
1856 :param limit: optional max number of records to return
1857 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1859 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1860 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1863 return self._name_search(cr, user, name, args, operator, context, limit)
1865 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1866 # solve some access rights issues
1867 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1874 args += [(self._rec_name, operator, name)]
1875 access_rights_uid = name_get_uid or user
1876 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1877 res = self.name_get(cr, access_rights_uid, ids, context)
1880 def copy(self, cr, uid, id, default=None, context=None):
1881 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1883 def exists(self, cr, uid, id, context=None):
1884 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1886 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1889 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1891 fields = self._columns.keys() + self._inherit_fields.keys()
1892 #FIXME: collect all calls to _get_source into one SQL call.
1894 res[lang] = {'code': lang}
1896 if f in self._columns:
1897 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1899 res[lang][f] = res_trans
1901 res[lang][f] = self._columns[f].string
1902 for table in self._inherits:
1903 cols = intersect(self._inherit_fields.keys(), fields)
1904 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1907 res[lang]['code'] = lang
1908 for f in res2[lang]:
1909 res[lang][f] = res2[lang][f]
1912 def write_string(self, cr, uid, id, langs, vals, context=None):
1913 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1914 #FIXME: try to only call the translation in one SQL
1917 if field in self._columns:
1918 src = self._columns[field].string
1919 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1920 for table in self._inherits:
1921 cols = intersect(self._inherit_fields.keys(), vals)
1923 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1926 def _check_removed_columns(self, cr, log=False):
1927 raise NotImplementedError()
1929 def _add_missing_default_values(self, cr, uid, values, context=None):
1930 missing_defaults = []
1931 avoid_tables = [] # avoid overriding inherited values when parent is set
1932 for tables, parent_field in self._inherits.items():
1933 if parent_field in values:
1934 avoid_tables.append(tables)
1935 for field in self._columns.keys():
1936 if not field in values:
1937 missing_defaults.append(field)
1938 for field in self._inherit_fields.keys():
1939 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1940 missing_defaults.append(field)
1942 if len(missing_defaults):
1943 # override defaults with the provided values, never allow the other way around
1944 defaults = self.default_get(cr, uid, missing_defaults, context)
1946 # FIXME: also handle inherited m2m
1947 if dv in self._columns and self._columns[dv]._type == 'many2many' \
1948 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1949 defaults[dv] = [(6, 0, defaults[dv])]
1950 defaults.update(values)
1954 class orm_memory(orm_template):
1956 _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']
1957 _inherit_fields = {}
1962 def __init__(self, cr):
1963 super(orm_memory, self).__init__(cr)
1967 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1969 def _check_access(self, uid, object_id, mode):
1970 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1971 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1973 def vaccum(self, cr, uid):
1975 if self.check_id % self._check_time:
1978 max = time.time() - self._max_hours * 60 * 60
1979 for id in self.datas:
1980 if self.datas[id]['internal.date_access'] < max:
1982 self.unlink(cr, 1, tounlink)
1983 if len(self.datas)>self._max_count:
1984 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1986 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1987 self.unlink(cr, uid, ids)
1990 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1993 if not fields_to_read:
1994 fields_to_read = self._columns.keys()
1998 if isinstance(ids, (int, long)):
2002 for f in fields_to_read:
2003 record = self.datas.get(id)
2005 self._check_access(user, id, 'read')
2006 r[f] = record.get(f, False)
2007 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2010 if id in self.datas:
2011 self.datas[id]['internal.date_access'] = time.time()
2012 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2013 for f in fields_post:
2014 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2015 for record in result:
2016 record[f] = res2[record['id']]
2017 if isinstance(ids_orig, (int, long)):
2021 def write(self, cr, user, ids, vals, context=None):
2027 if self._columns[field]._classic_write:
2028 vals2[field] = vals[field]
2030 upd_todo.append(field)
2031 for object_id in ids:
2032 self._check_access(user, object_id, mode='write')
2033 self.datas[object_id].update(vals2)
2034 self.datas[object_id]['internal.date_access'] = time.time()
2035 for field in upd_todo:
2036 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2037 self._validate(cr, user, [object_id], context)
2038 wf_service = netsvc.LocalService("workflow")
2039 wf_service.trg_write(user, self._name, object_id, cr)
2042 def create(self, cr, user, vals, context=None):
2043 self.vaccum(cr, user)
2045 id_new = self.next_id
2047 vals = self._add_missing_default_values(cr, user, vals, context)
2052 if self._columns[field]._classic_write:
2053 vals2[field] = vals[field]
2055 upd_todo.append(field)
2056 self.datas[id_new] = vals2
2057 self.datas[id_new]['internal.date_access'] = time.time()
2058 self.datas[id_new]['internal.create_uid'] = user
2060 for field in upd_todo:
2061 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2062 self._validate(cr, user, [id_new], context)
2063 if self._log_create and not (context and context.get('no_store_function', False)):
2064 message = self._description + \
2066 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2068 self.log(cr, user, id_new, message, True, context=context)
2069 wf_service = netsvc.LocalService("workflow")
2070 wf_service.trg_create(user, self._name, id_new, cr)
2073 def _where_calc(self, cr, user, args, active_test=True, context=None):
2078 # if the object has a field named 'active', filter out all inactive
2079 # records unless they were explicitely asked for
2080 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2082 active_in_args = False
2084 if a[0] == 'active':
2085 active_in_args = True
2086 if not active_in_args:
2087 args.insert(0, ('active', '=', 1))
2089 args = [('active', '=', 1)]
2092 e = expression.expression(args)
2093 e.parse(cr, user, self, context)
2097 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2101 # implicit filter on current user except for superuser
2105 args.insert(0, ('internal.create_uid', '=', user))
2107 result = self._where_calc(cr, user, args, context=context)
2109 return self.datas.keys()
2113 #Find the value of dict
2116 for id, data in self.datas.items():
2119 if limit and (counter > int(limit)):
2124 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2125 elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
2126 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2127 elif arg[1] in ['ilike']:
2128 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2138 def unlink(self, cr, uid, ids, context=None):
2140 self._check_access(uid, id, 'unlink')
2141 self.datas.pop(id, None)
2143 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2146 def perm_read(self, cr, user, ids, context=None, details=True):
2148 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2149 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2151 self._check_access(user, id, 'read')
2153 'create_uid': credentials,
2154 'create_date': create_date,
2156 'write_date': False,
2161 def _check_removed_columns(self, cr, log=False):
2162 # nothing to check in memory...
2165 def exists(self, cr, uid, id, context=None):
2166 return id in self.datas
2168 class orm(orm_template):
2169 _sql_constraints = []
2171 _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']
2173 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2175 Get the list of records in list view grouped by the given ``groupby`` fields
2177 :param cr: database cursor
2178 :param uid: current user id
2179 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2180 :param fields: list of fields present in the list view specified on the object
2181 :param groupby: list of fields on which to groupby the records
2182 :type fields_list: list (example ['field_name_1', ...])
2183 :param offset: optional number of records to skip
2184 :param limit: optional max number of records to return
2185 :param context: context arguments, like lang, time zone
2186 :return: list of dictionaries(one dictionary for each record) containing:
2188 * the values of fields grouped by the fields in ``groupby`` argument
2189 * __domain: list of tuples specifying the search criteria
2190 * __context: dictionary with argument like ``groupby``
2191 :rtype: [{'field_name_1': value, ...]
2192 :raise AccessError: * if user has no read rights on the requested object
2193 * if user tries to bypass access rules for read on the requested object
2196 context = context or {}
2197 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2199 fields = self._columns.keys()
2201 # compute the where, order by, limit and offset clauses
2202 (where_clause, where_clause_params, tables) = self._where_calc(cr, uid, domain, context=context)
2204 # apply direct ir.rules from current model
2205 self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', context=context)
2207 # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
2208 for inherited_model in self._inherits:
2209 previous_tables = list(tables)
2210 if self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
2211 # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
2212 # list of table in case the inherited table was not in the list before (as that means the corresponding
2213 # JOIN(s) was(were) not present)
2214 self._inherits_join_add(inherited_model, previous_tables, where_clause)
2215 tables = list(set(tables).union(set(previous_tables)))
2217 # Take care of adding join(s) if groupby is an '_inherits'ed field
2218 groupby_list = groupby
2220 if groupby and isinstance(groupby, list):
2221 groupby = groupby[0]
2222 tables, where_clause, qfield = self._inherits_join_calc(groupby,tables,where_clause)
2224 if len(where_clause):
2225 where_clause = ' where ' + ' and '.join(where_clause)
2228 limit_str = limit and ' limit %d' % limit or ''
2229 offset_str = offset and ' offset %d' % offset or ''
2231 fget = self.fields_get(cr, uid, fields)
2232 float_int_fields = filter(lambda x: fget[x]['type'] in ('float','integer'), fields)
2238 if fget.get(groupby,False) and fget[groupby]['type'] in ('date','datetime'):
2239 flist = "to_char(%s,'yyyy-mm') as %s "%(groupby,groupby)
2240 groupby = "to_char(%s,'yyyy-mm')"%(groupby)
2245 fields_pre = [f for f in float_int_fields if
2246 f == self.CONCURRENCY_CHECK_FIELD
2247 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2248 for f in fields_pre:
2249 if f not in ['id','sequence']:
2250 operator = fget[f].get('group_operator','sum')
2253 flist += operator+'('+f+') as '+f
2256 gb = ' group by '+groupby
2259 cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_clause_params)
2262 for r in cr.dictfetchall():
2263 for fld,val in r.items():
2264 if val == None:r[fld] = False
2265 alldata[r['id']] = r
2267 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2270 d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
2271 if not isinstance(groupby_list,(str, unicode)):
2272 if groupby or not context.get('group_by_no_leaf', False):
2273 d['__context'] = {'group_by':groupby_list[1:]}
2274 if groupby and groupby in fget:
2275 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
2276 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
2277 days = calendar.monthrange(dt.year, dt.month)[1]
2279 d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
2280 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),\
2281 (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
2282 del alldata[d['id']][groupby]
2283 d.update(alldata[d['id']])
2287 def _inherits_join_add(self, parent_model_name, tables, where_clause):
2289 Add missing table SELECT and JOIN clause for reaching the parent table (no duplicates)
2291 :param parent_model_name: name of the parent model for which the clauses should be added
2292 :param tables: list of table._table names enclosed in double quotes as returned
2294 :param where_clause: current list of WHERE clause params
2296 inherits_field = self._inherits[parent_model_name]
2297 parent_model = self.pool.get(parent_model_name)
2298 parent_table_name = parent_model._table
2299 quoted_parent_table_name = '"%s"' % parent_table_name
2300 if quoted_parent_table_name not in tables:
2301 tables.append(quoted_parent_table_name)
2302 where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2303 return (tables, where_clause)
2305 def _inherits_join_calc(self, field, tables, where_clause):
2307 Adds missing table select and join clause(s) for reaching
2308 the field coming from an '_inherits' parent table (no duplicates).
2310 :param tables: list of table._table names enclosed in double quotes as returned
2312 :param where_clause: current list of WHERE clause params
2313 :return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated
2314 versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
2315 in the form ``table.field``, to be referenced in queries.
2317 current_table = self
2318 while field in current_table._inherit_fields and not field in current_table._columns:
2319 parent_model_name = current_table._inherit_fields[field][0]
2320 parent_table = self.pool.get(parent_model_name)
2321 self._inherits_join_add(parent_model_name, tables, where_clause)
2322 current_table = parent_table
2323 return (tables, where_clause, '"%s".%s' % (current_table._table, field))
2325 def _parent_store_compute(self, cr):
2326 if not self._parent_store:
2328 logger = netsvc.Logger()
2329 logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2330 def browse_rec(root, pos=0):
2332 where = self._parent_name+'='+str(root)
2334 where = self._parent_name+' IS NULL'
2335 if self._parent_order:
2336 where += ' order by '+self._parent_order
2337 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2339 childs = cr.fetchall()
2341 pos2 = browse_rec(id[0], pos2)
2342 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
2344 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2345 if self._parent_order:
2346 query += ' order by '+self._parent_order
2349 for (root,) in cr.fetchall():
2350 pos = browse_rec(root, pos)
2353 def _update_store(self, cr, f, k):
2354 logger = netsvc.Logger()
2355 logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2356 ss = self._columns[k]._symbol_set
2357 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2358 cr.execute('select id from '+self._table)
2359 ids_lst = map(lambda x: x[0], cr.fetchall())
2362 ids_lst = ids_lst[40:]
2363 res = f.get(cr, self, iids, k, 1, {})
2364 for key,val in res.items():
2367 # if val is a many2one, just write the ID
2368 if type(val)==tuple:
2370 if (val<>False) or (type(val)<>bool):
2371 cr.execute(update_query, (ss[1](val), key))
2373 def _check_removed_columns(self, cr, log=False):
2374 logger = netsvc.Logger()
2375 # iterate on the database columns to drop the NOT NULL constraints
2376 # of fields which were required but have been removed (or will be added by another module)
2377 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2378 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2379 cr.execute("SELECT a.attname, a.attnotnull"
2380 " FROM pg_class c, pg_attribute a"
2381 " WHERE c.relname=%s"
2382 " AND c.oid=a.attrelid"
2383 " AND a.attisdropped=%s"
2384 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2385 " AND a.attname NOT IN %s" ,(self._table, False, tuple(columns))),
2387 for column in cr.dictfetchall():
2389 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))
2390 if column['attnotnull']:
2391 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2393 def _auto_init(self, cr, context={}):
2394 store_compute = False
2395 logger = netsvc.Logger()
2398 self._field_create(cr, context=context)
2399 if getattr(self, '_auto', True):
2400 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s" ,( self._table,))
2402 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2403 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2406 if self._parent_store:
2407 cr.execute("""SELECT c.relname
2408 FROM pg_class c, pg_attribute a
2409 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2410 """, (self._table, 'parent_left'))
2412 if 'parent_left' not in self._columns:
2413 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2414 if 'parent_right' not in self._columns:
2415 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2416 if self._columns[self._parent_name].ondelete != 'cascade':
2417 logger.notifyChannel('orm', netsvc.LOG_ERROR, "The column %s on object %s must be set as ondelete='cascade'" % (self._parent_name, self._name))
2418 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2419 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2421 store_compute = True
2423 if self._log_access:
2425 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2426 'create_date': 'TIMESTAMP',
2427 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2428 'write_date': 'TIMESTAMP'
2433 FROM pg_class c, pg_attribute a
2434 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2435 """, (self._table, k))
2437 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2440 self._check_removed_columns(cr, log=False)
2442 # iterate on the "object columns"
2443 todo_update_store = []
2444 update_custom_fields = context.get('update_custom_fields', False)
2445 for k in self._columns:
2446 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2448 #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2449 #Not Updating Custom fields
2450 if k.startswith('x_') and not update_custom_fields:
2452 f = self._columns[k]
2454 if isinstance(f, fields.one2many):
2455 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2457 if self.pool.get(f._obj):
2458 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2459 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2460 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2463 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))
2464 res = cr.fetchone()[0]
2466 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2467 elif isinstance(f, fields.many2many):
2468 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2469 if not cr.dictfetchall():
2470 if not self.pool.get(f._obj):
2471 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2472 ref = self.pool.get(f._obj)._table
2473 # ref = f._obj.replace('.', '_')
2474 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))
2475 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2476 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2477 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2480 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 " \
2481 "FROM pg_class c,pg_attribute a,pg_type t " \
2482 "WHERE c.relname=%s " \
2483 "AND a.attname=%s " \
2484 "AND c.oid=a.attrelid " \
2485 "AND a.atttypid=t.oid", (self._table, k))
2486 res = cr.dictfetchall()
2487 if not res and hasattr(f,'oldname'):
2488 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 " \
2489 "FROM pg_class c,pg_attribute a,pg_type t " \
2490 "WHERE c.relname=%s " \
2491 "AND a.attname=%s " \
2492 "AND c.oid=a.attrelid " \
2493 "AND a.atttypid=t.oid", (self._table, f.oldname))
2494 res_old = cr.dictfetchall()
2495 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2496 if res_old and len(res_old)==1:
2497 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2499 res[0]['attname'] = k
2504 f_pg_type = f_pg_def['typname']
2505 f_pg_size = f_pg_def['size']
2506 f_pg_notnull = f_pg_def['attnotnull']
2507 if isinstance(f, fields.function) and not f.store and\
2508 not getattr(f, 'nodrop', False):
2509 logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2510 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2514 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2519 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2520 ('varchar', 'text', 'TEXT', ''),
2521 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2522 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2523 ('timestamp', 'date', 'date', '::date'),
2524 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2525 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2527 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2528 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2529 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2530 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2531 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2532 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2535 if (f_pg_type==c[0]) and (f._type==c[1]):
2536 if f_pg_type != f_obj_type:
2537 if f_pg_type != f_obj_type:
2538 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2540 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2541 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2542 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2543 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2547 if f_pg_type != f_obj_type:
2551 newname = self._table + '_moved' + str(i)
2552 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2553 "WHERE c.relname=%s " \
2554 "AND a.attname=%s " \
2555 "AND c.oid=a.attrelid ", (self._table, newname))
2556 if not cr.fetchone()[0]:
2559 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))
2561 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2562 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2563 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2564 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2566 # if the field is required and hasn't got a NOT NULL constraint
2567 if f.required and f_pg_notnull == 0:
2568 # set the field to the default value if any
2569 if k in self._defaults:
2570 if callable(self._defaults[k]):
2571 default = self._defaults[k](self, cr, 1, context)
2573 default = self._defaults[k]
2575 if (default is not None):
2576 ss = self._columns[k]._symbol_set
2577 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2578 cr.execute(query, (ss[1](default),))
2579 # add the NOT NULL constraint
2582 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2585 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))
2587 elif not f.required and f_pg_notnull == 1:
2588 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2592 indexname = '%s_%s_index' % (self._table, k)
2593 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2594 res2 = cr.dictfetchall()
2595 if not res2 and f.select:
2596 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2598 if f._type == 'text':
2599 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2600 logger.notifyChannel('orm', netsvc.LOG_WARNING, "Adding (b-tree) index for text column '%s' in table '%s'."\
2601 "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"\
2602 "Use a search view instead if you simply want to make the field searchable." % (k, f._type, self._table))
2603 if res2 and not f.select:
2604 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2606 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))
2608 if isinstance(f, fields.many2one):
2609 ref = self.pool.get(f._obj)._table
2610 if ref != 'ir_actions':
2611 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2612 'pg_attribute as att1, pg_attribute as att2 '
2613 'WHERE con.conrelid = cl1.oid '
2614 'AND cl1.relname = %s '
2615 'AND con.confrelid = cl2.oid '
2616 'AND cl2.relname = %s '
2617 'AND array_lower(con.conkey, 1) = 1 '
2618 'AND con.conkey[1] = att1.attnum '
2619 'AND att1.attrelid = cl1.oid '
2620 'AND att1.attname = %s '
2621 'AND array_lower(con.confkey, 1) = 1 '
2622 'AND con.confkey[1] = att2.attnum '
2623 'AND att2.attrelid = cl2.oid '
2624 'AND att2.attname = %s '
2625 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2626 res2 = cr.dictfetchall()
2628 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2629 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2630 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2633 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2635 if not isinstance(f, fields.function) or f.store:
2637 # add the missing field
2638 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2639 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2642 if not create and k in self._defaults:
2643 if callable(self._defaults[k]):
2644 default = self._defaults[k](self, cr, 1, context)
2646 default = self._defaults[k]
2648 ss = self._columns[k]._symbol_set
2649 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2650 cr.execute(query, (ss[1](default),))
2652 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2654 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2656 if isinstance(f, fields.function):
2658 if f.store is not True:
2659 order = f.store[f.store.keys()[0]][2]
2660 todo_update_store.append((order, f,k))
2662 # and add constraints if needed
2663 if isinstance(f, fields.many2one):
2664 if not self.pool.get(f._obj):
2665 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2666 ref = self.pool.get(f._obj)._table
2667 # ref = f._obj.replace('.', '_')
2668 # ir_actions is inherited so foreign key doesn't work on it
2669 if ref != 'ir_actions':
2670 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2672 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2676 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2678 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))
2680 for order,f,k in todo_update_store:
2681 todo_end.append((order, self._update_store, (f, k)))
2684 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2685 create = not bool(cr.fetchone())
2687 cr.commit() # start a new transaction
2689 for (key, con, _) in self._sql_constraints:
2690 conname = '%s_%s' % (self._table, key)
2691 cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2692 if not cr.dictfetchall():
2693 query = 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,)
2698 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))
2702 if hasattr(self, "_sql"):
2703 for line in self._sql.split(';'):
2704 line2 = line.replace('\n', '').strip()
2709 self._parent_store_compute(cr)
2713 def __init__(self, cr):
2714 super(orm, self).__init__(cr)
2716 if not hasattr(self, '_log_access'):
2717 # if not access is not specify, it is the same value as _auto
2718 self._log_access = getattr(self, "_auto", True)
2720 self._columns = self._columns.copy()
2721 for store_field in self._columns:
2722 f = self._columns[store_field]
2723 if hasattr(f, 'digits_change'):
2725 if not isinstance(f, fields.function):
2729 if self._columns[store_field].store is True:
2730 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2732 sm = self._columns[store_field].store
2733 for object, aa in sm.items():
2735 (fnct,fields2,order,length)=aa
2737 (fnct,fields2,order)=aa
2740 raise except_orm('Error',
2741 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2742 self.pool._store_function.setdefault(object, [])
2744 for x,y,z,e,f,l in self.pool._store_function[object]:
2745 if (x==self._name) and (y==store_field) and (e==fields2):
2749 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2750 self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2752 for (key, _, msg) in self._sql_constraints:
2753 self.pool._sql_error[self._table+'_'+key] = msg
2755 # Load manual fields
2757 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2759 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2760 for field in cr.dictfetchall():
2761 if field['name'] in self._columns:
2764 'string': field['field_description'],
2765 'required': bool(field['required']),
2766 'readonly': bool(field['readonly']),
2767 'domain': field['domain'] or None,
2768 'size': field['size'],
2769 'ondelete': field['on_delete'],
2770 'translate': (field['translate']),
2771 #'select': int(field['select_level'])
2774 if field['ttype'] == 'selection':
2775 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2776 elif field['ttype'] == 'reference':
2777 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2778 elif field['ttype'] == 'many2one':
2779 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2780 elif field['ttype'] == 'one2many':
2781 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2782 elif field['ttype'] == 'many2many':
2783 _rel1 = field['relation'].replace('.', '_')
2784 _rel2 = field['model'].replace('.', '_')
2785 _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2786 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2788 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2789 self._inherits_check()
2790 self._inherits_reload()
2791 if not self._sequence:
2792 self._sequence = self._table+'_id_seq'
2793 for k in self._defaults:
2794 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,)
2795 for f in self._columns:
2796 self._columns[f].restart()
2799 # Update objects that uses this one to update their _inherits fields
2802 def _inherits_reload_src(self):
2803 for obj in self.pool.obj_pool.values():
2804 if self._name in obj._inherits:
2805 obj._inherits_reload()
2807 def _inherits_reload(self):
2809 for table in self._inherits:
2810 res.update(self.pool.get(table)._inherit_fields)
2811 for col in self.pool.get(table)._columns.keys():
2812 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2813 for col in self.pool.get(table)._inherit_fields.keys():
2814 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2815 self._inherit_fields = res
2816 self._inherits_reload_src()
2818 def _inherits_check(self):
2819 for table, field_name in self._inherits.items():
2820 if field_name not in self._columns:
2821 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2822 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2823 required=True, ondelete="cascade")
2824 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2825 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))
2826 self._columns[field_name].required = True
2827 self._columns[field_name].ondelete = "cascade"
2829 #def __getattr__(self, name):
2831 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2832 # (though inherits doesn't use Python inheritance).
2833 # Handles translating between local ids and remote ids.
2834 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2835 # when you have inherits.
2837 # for model, field in self._inherits.iteritems():
2838 # proxy = self.pool.get(model)
2839 # if hasattr(proxy, name):
2840 # attribute = getattr(proxy, name)
2841 # if not hasattr(attribute, '__call__'):
2845 # return super(orm, self).__getattr__(name)
2847 # def _proxy(cr, uid, ids, *args, **kwargs):
2848 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2849 # lst = [obj[field].id for obj in objects if obj[field]]
2850 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2855 def fields_get(self, cr, user, fields=None, context=None):
2857 Get the description of list of fields
2859 :param cr: database cursor
2860 :param user: current user id
2861 :param fields: list of fields
2862 :param context: context arguments, like lang, time zone
2863 :return: dictionary of field dictionaries, each one describing a field of the business object
2864 :raise AccessError: * if user has no create/write rights on the requested object
2867 ira = self.pool.get('ir.model.access')
2868 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2869 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2870 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2872 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2874 Read records with given ids with the given fields
2876 :param cr: database cursor
2877 :param user: current user id
2878 :param ids: id or list of the ids of the records to read
2879 :param fields: optional list of field names to return (default: all fields would be returned)
2880 :type fields: list (example ['field_name_1', ...])
2881 :param context: (optional) context arguments, like lang, time zone
2882 :return: list of dictionaries((dictionary per record asked)) with requested field values
2883 :rtype: [{‘name_of_the_field’: value, ...}, ...]
2884 :raise AccessError: * if user has no read rights on the requested object
2885 * if user tries to bypass access rules for read on the requested object
2890 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2892 fields = self._columns.keys() + self._inherit_fields.keys()
2893 if isinstance(ids, (int, long)):
2897 select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2898 result = self._read_flat(cr, user, select, fields, context, load)
2901 for key, v in r.items():
2904 if key in self._columns:
2905 column = self._columns[key]
2906 elif key in self._inherit_fields:
2907 column = self._inherit_fields[key][2]
2910 if v and column._type == 'reference':
2911 model_name, ref_id = v.split(',', 1)
2912 model = self.pool.get(model_name)
2916 cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
2917 reset = not cr.fetchone()[0]
2919 if column._classic_write:
2920 query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
2921 cr.execute(query, (r['id'],))
2924 if isinstance(ids, (int, long, dict)):
2925 return result and result[0] or False
2928 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2933 if fields_to_read == None:
2934 fields_to_read = self._columns.keys()
2936 # Construct a clause for the security rules.
2937 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2938 # or will at least contain self._table.
2939 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2941 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2942 fields_pre = [f for f in fields_to_read if
2943 f == self.CONCURRENCY_CHECK_FIELD
2944 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2945 ] + self._inherits.values()
2949 def convert_field(f):
2950 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2951 if f in ('create_date', 'write_date'):
2952 return "date_trunc('second', %s) as %s" % (f_qual, f)
2953 if f == self.CONCURRENCY_CHECK_FIELD:
2954 if self._log_access:
2955 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2956 return "now()::timestamp AS %s" % (f,)
2957 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2958 return 'length(%s) as "%s"' % (f_qual, f)
2961 fields_pre2 = map(convert_field, fields_pre)
2962 order_by = self._parent_order or self._order
2963 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2964 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2966 query += " AND " + (' OR '.join(rule_clause))
2967 query += " ORDER BY " + order_by
2968 for sub_ids in cr.split_for_in_conditions(ids):
2970 cr.execute(query, [tuple(sub_ids)] + rule_params)
2971 if cr.rowcount != len(sub_ids):
2972 raise except_orm(_('AccessError'),
2973 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2975 cr.execute(query, (tuple(sub_ids),))
2976 res.extend(cr.dictfetchall())
2978 res = map(lambda x: {'id': x}, ids)
2980 for f in fields_pre:
2981 if f == self.CONCURRENCY_CHECK_FIELD:
2983 if self._columns[f].translate:
2984 ids = [x['id'] for x in res]
2985 #TODO: optimize out of this loop
2986 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2988 r[f] = res_trans.get(r['id'], False) or r[f]
2990 for table in self._inherits:
2991 col = self._inherits[table]
2992 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
2995 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3003 if not record[col]:# if the record is deleted from _inherits table?
3005 record.update(res3[record[col]])
3006 if col not in fields_to_read:
3009 # all fields which need to be post-processed by a simple function (symbol_get)
3010 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3013 for f in fields_post:
3014 r[f] = self._columns[f]._symbol_get(r[f])
3015 ids = [x['id'] for x in res]
3017 # all non inherited fields for which the attribute whose name is in load is False
3018 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3020 # Compute POST fields
3022 for f in fields_post:
3023 todo.setdefault(self._columns[f]._multi, [])
3024 todo[self._columns[f]._multi].append(f)
3025 for key,val in todo.items():
3027 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3030 if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3031 record[pos] = res2[record['id']][pos]
3034 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3037 record[f] = res2[record['id']]
3042 for field in vals.copy():
3044 if field in self._columns:
3045 fobj = self._columns[field]
3052 for group in groups:
3053 module = group.split(".")[0]
3054 grp = group.split(".")[1]
3055 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" \
3056 (grp, module, 'res.groups', user))
3057 readonly = cr.fetchall()
3058 if readonly[0][0] >= 1:
3061 elif readonly[0][0] == 0:
3067 if type(vals[field]) == type([]):
3069 elif type(vals[field]) == type(0.0):
3071 elif type(vals[field]) == type(''):
3072 vals[field] = '=No Permission='
3077 def perm_read(self, cr, user, ids, context=None, details=True):
3079 Read the permission for record of the given ids
3081 :param cr: database cursor
3082 :param user: current user id
3083 :param ids: id or list of ids
3084 :param context: context arguments, like lang, time zone
3085 :param details: if True, \*_uid fields are replaced with the name of the user
3086 :return: list of ownership dictionaries for each requested record
3087 :rtype: list of dictionaries with the following keys:
3090 * create_uid: user who created the record
3091 * create_date: date when the record was created
3092 * write_uid: last user who changed the record
3093 * write_date: date of the last change to the record
3101 uniq = isinstance(ids, (int, long))
3105 if self._log_access:
3106 fields += ', create_uid, create_date, write_uid, write_date'
3107 query = 'SELECT %s FROM "%s" WHERE id IN %%s' % (fields, self._table)
3108 cr.execute(query, (tuple(ids),))
3109 res = cr.dictfetchall()
3112 r[key] = r[key] or False
3113 if key in ('write_uid', 'create_uid', 'uid') and details:
3115 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3120 def _check_concurrency(self, cr, ids, context):
3123 if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
3125 return "%s,%s" % (self._name, oid)
3126 santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3127 for i in range(0, len(ids), cr.IN_MAX):
3128 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
3129 for oid in ids[i:i+cr.IN_MAX]
3130 if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
3132 cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
3135 raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
3137 def check_access_rule(self, cr, uid, ids, operation, context=None):
3138 """Verifies that the operation given by ``operation`` is allowed for the user
3139 according to ir.rules.
3141 :param operation: one of ``write``, ``unlink``
3142 :raise except_orm: * if current ir.rules do not permit this operation.
3143 :return: None if the operation is allowed
3145 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3147 where_clause = ' and ' + ' and '.join(where_clause)
3148 for sub_ids in cr.split_for_in_conditions(ids):
3149 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3150 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3151 [sub_ids] + where_params)
3152 if cr.rowcount != len(sub_ids):
3153 raise except_orm(_('AccessError'),
3154 _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
3155 % (operation, self._name))
3157 def unlink(self, cr, uid, ids, context=None):
3159 Delete records with given ids
3161 :param cr: database cursor
3162 :param uid: current user id
3163 :param ids: id or list of ids
3164 :param context: (optional) context arguments, like lang, time zone
3166 :raise AccessError: * if user has no unlink rights on the requested object
3167 * if user tries to bypass access rules for unlink on the requested object
3168 :raise UserError: if the record is default property for other records
3173 if isinstance(ids, (int, long)):
3176 result_store = self._store_get_values(cr, uid, ids, None, context)
3178 self._check_concurrency(cr, ids, context)
3180 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3182 properties = self.pool.get('ir.property')
3183 domain = [('res_id', '=', False),
3184 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3186 if properties.search(cr, uid, domain, context=context):
3187 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3189 wf_service = netsvc.LocalService("workflow")
3191 wf_service.trg_delete(uid, self._name, oid, cr)
3194 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3195 for sub_ids in cr.split_for_in_conditions(ids):
3196 cr.execute('delete from ' + self._table + ' ' \
3197 'where id IN %s', (sub_ids,))
3198 for order, object, store_ids, fields in result_store:
3199 if object != self._name:
3200 obj = self.pool.get(object)
3201 cr.execute('select id from '+obj._table+' where id IN %s',(tuple(store_ids),))
3202 rids = map(lambda x: x[0], cr.fetchall())
3204 obj._store_set_values(cr, uid, rids, fields, context)
3210 def write(self, cr, user, ids, vals, context=None):
3212 Update records with given ids with the given field values
3214 :param cr: database cursor
3215 :param user: current user id
3217 :param ids: object id or list of object ids to update according to **vals**
3218 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3219 :type vals: dictionary
3220 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3221 :type context: dictionary
3223 :raise AccessError: * if user has no write rights on the requested object
3224 * if user tries to bypass access rules for write on the requested object
3225 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3226 :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)
3228 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3230 + For a many2many field, a list of tuples is expected.
3231 Here is the list of tuple that are accepted, with the corresponding semantics ::
3233 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3234 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3235 (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)
3236 (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)
3237 (4, ID) link to existing record with id = ID (adds a relationship)
3238 (5) unlink all (like using (3,ID) for all linked records)
3239 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3242 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3244 + For a one2many field, a lits of tuples is expected.
3245 Here is the list of tuple that are accepted, with the corresponding semantics ::
3247 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3248 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3249 (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)
3252 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3254 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3255 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3259 for field in vals.copy():
3261 if field in self._columns:
3262 fobj = self._columns[field]
3264 fobj = self._inherit_fields[field][2]
3271 for group in groups:
3272 module = group.split(".")[0]
3273 grp = group.split(".")[1]
3274 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", \
3275 (grp, module, 'res.groups', user))
3276 readonly = cr.fetchall()
3277 if readonly[0][0] >= 1:
3280 elif readonly[0][0] == 0:
3292 if isinstance(ids, (int, long)):
3295 self._check_concurrency(cr, ids, context)
3296 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3298 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3300 # No direct update of parent_left/right
3301 vals.pop('parent_left', None)
3302 vals.pop('parent_right', None)
3304 parents_changed = []
3305 if self._parent_store and (self._parent_name in vals):
3306 # The parent_left/right computation may take up to
3307 # 5 seconds. No need to recompute the values if the
3308 # parent is the same. Get the current value of the parent
3309 parent_val = vals[self._parent_name]
3311 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3312 (self._table, self._parent_name, self._parent_name)
3313 cr.execute(query, (tuple(ids), parent_val))
3315 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3316 (self._table, self._parent_name)
3317 cr.execute(query, (tuple(ids),))
3318 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3325 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3327 if field in self._columns:
3328 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3329 if (not totranslate) or not self._columns[field].translate:
3330 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3331 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3332 direct.append(field)
3334 upd_todo.append(field)
3336 updend.append(field)
3337 if field in self._columns \
3338 and hasattr(self._columns[field], 'selection') \
3340 if self._columns[field]._type == 'reference':
3341 val = vals[field].split(',')[0]
3344 if isinstance(self._columns[field].selection, (tuple, list)):
3345 if val not in dict(self._columns[field].selection):
3346 raise except_orm(_('ValidateError'),
3347 _('The value "%s" for the field "%s" is not in the selection') \
3348 % (vals[field], field))
3350 if val not in dict(self._columns[field].selection(
3351 self, cr, user, context=context)):
3352 raise except_orm(_('ValidateError'),
3353 _('The value "%s" for the field "%s" is not in the selection') \
3354 % (vals[field], field))
3356 if self._log_access:
3357 upd0.append('write_uid=%s')
3358 upd0.append('write_date=now()')
3362 self.check_access_rule(cr, user, ids, 'write', context=context)
3363 for sub_ids in cr.split_for_in_conditions(ids):
3364 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3365 'where id IN %s', upd1 + [sub_ids])
3370 if self._columns[f].translate:
3371 src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
3374 # Inserting value to DB
3375 self.write(cr, user, ids, {f:vals[f]})
3376 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3379 # call the 'set' method of fields which are not classic_write
3380 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3382 # default element in context must be removed when call a one2many or many2many
3383 rel_context = context.copy()
3384 for c in context.items():
3385 if c[0].startswith('default_'):
3386 del rel_context[c[0]]
3388 for field in upd_todo:
3390 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3392 for table in self._inherits:
3393 col = self._inherits[table]
3395 for sub_ids in cr.split_for_in_conditions(ids):
3396 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3397 'where id IN %s', (sub_ids,))
3398 nids.extend([x[0] for x in cr.fetchall()])
3402 if self._inherit_fields[val][0] == table:
3404 self.pool.get(table).write(cr, user, nids, v, context)
3406 self._validate(cr, user, ids, context)
3408 # TODO: use _order to set dest at the right position and not first node of parent
3409 # We can't defer parent_store computation because the stored function
3410 # fields that are computer may refer (directly or indirectly) to
3411 # parent_left/right (via a child_of domain)
3414 self.pool._init_parent[self._name]=True
3416 order = self._parent_order or self._order
3417 parent_val = vals[self._parent_name]
3419 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3421 clause, params = '%s IS NULL' % (self._parent_name,), ()
3422 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3423 parents = cr.fetchall()
3425 for id in parents_changed:
3426 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3427 pleft, pright = cr.fetchone()
3428 distance = pright - pleft + 1
3430 # Find Position of the element
3432 for (parent_pright, parent_id) in parents:
3435 position = parent_pright+1
3437 # It's the first node of the parent
3442 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3443 position = cr.fetchone()[0]+1
3445 if pleft < position <= pright:
3446 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3448 if pleft < position:
3449 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3450 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3451 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))
3453 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3454 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3455 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))
3457 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3461 for order, object, ids, fields in result:
3462 key = (object,tuple(fields))
3463 done.setdefault(key, {})
3464 # avoid to do several times the same computation
3467 if id not in done[key]:
3468 done[key][id] = True
3470 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3472 wf_service = netsvc.LocalService("workflow")
3474 wf_service.trg_write(user, self._name, id, cr)
3478 # TODO: Should set perm to user.xxx
3480 def create(self, cr, user, vals, context=None):
3482 Create new record with specified value
3484 :param cr: database cursor
3485 :param user: current user id
3487 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3488 :type vals: dictionary
3489 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3490 :type context: dictionary
3491 :return: id of new record created
3492 :raise AccessError: * if user has no create rights on the requested object
3493 * if user tries to bypass access rules for create on the requested object
3494 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3495 :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)
3497 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3498 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3504 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3506 vals = self._add_missing_default_values(cr, user, vals, context)
3509 for v in self._inherits:
3510 if self._inherits[v] not in vals:
3513 tocreate[v] = {'id' : vals[self._inherits[v]]}
3514 (upd0, upd1, upd2) = ('', '', [])
3516 for v in vals.keys():
3517 if v in self._inherit_fields:
3518 (table, col, col_detail) = self._inherit_fields[v]
3519 tocreate[table][v] = vals[v]
3522 if (v not in self._inherit_fields) and (v not in self._columns):
3525 # Try-except added to filter the creation of those records whose filds are readonly.
3526 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3528 cr.execute("SELECT nextval('"+self._sequence+"')")
3530 raise except_orm(_('UserError'),
3531 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3533 id_new = cr.fetchone()[0]
3534 for table in tocreate:
3535 if self._inherits[table] in vals:
3536 del vals[self._inherits[table]]
3538 record_id = tocreate[table].pop('id', None)
3540 if record_id is None or not record_id:
3541 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3543 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3545 upd0 += ','+self._inherits[table]
3547 upd2.append(record_id)
3549 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3550 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3552 for bool_field in bool_fields:
3553 if bool_field not in vals:
3554 vals[bool_field] = False
3556 for field in vals.copy():
3558 if field in self._columns:
3559 fobj = self._columns[field]
3561 fobj = self._inherit_fields[field][2]
3567 for group in groups:
3568 module = group.split(".")[0]
3569 grp = group.split(".")[1]
3570 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" % \
3571 (grp, module, 'res.groups', user))
3572 readonly = cr.fetchall()
3573 if readonly[0][0] >= 1:
3576 elif readonly[0][0] == 0:
3584 if self._columns[field]._classic_write:
3585 upd0 = upd0 + ',"' + field + '"'
3586 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3587 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3589 if not isinstance(self._columns[field], fields.related):
3590 upd_todo.append(field)
3591 if field in self._columns \
3592 and hasattr(self._columns[field], 'selection') \
3594 if self._columns[field]._type == 'reference':
3595 val = vals[field].split(',')[0]
3598 if isinstance(self._columns[field].selection, (tuple, list)):
3599 if val not in dict(self._columns[field].selection):
3600 raise except_orm(_('ValidateError'),
3601 _('The value "%s" for the field "%s" is not in the selection') \
3602 % (vals[field], field))
3604 if val not in dict(self._columns[field].selection(
3605 self, cr, user, context=context)):
3606 raise except_orm(_('ValidateError'),
3607 _('The value "%s" for the field "%s" is not in the selection') \
3608 % (vals[field], field))
3609 if self._log_access:
3610 upd0 += ',create_uid,create_date'
3613 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3614 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3615 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3617 if self._parent_store and not context.get('defer_parent_store_computation'):
3619 self.pool._init_parent[self._name]=True
3621 parent = vals.get(self._parent_name, False)
3623 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3625 result_p = cr.fetchall()
3626 for (pleft,) in result_p:
3631 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3632 pleft_old = cr.fetchone()[0]
3635 cr.execute('select max(parent_right) from '+self._table)
3636 pleft = cr.fetchone()[0] or 0
3637 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3638 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3639 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3641 # default element in context must be remove when call a one2many or many2many
3642 rel_context = context.copy()
3643 for c in context.items():
3644 if c[0].startswith('default_'):
3645 del rel_context[c[0]]
3648 for field in upd_todo:
3649 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3650 self._validate(cr, user, [id_new], context)
3652 if not context.get('no_store_function', False):
3653 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3656 for order, object, ids, fields2 in result:
3657 if not (object, ids, fields2) in done:
3658 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3659 done.append((object, ids, fields2))
3661 if self._log_create and not (context and context.get('no_store_function', False)):
3662 message = self._description + \
3664 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3666 self.log(cr, user, id_new, message, True, context=context)
3667 wf_service = netsvc.LocalService("workflow")
3668 wf_service.trg_create(user, self._name, id_new, cr)
3671 def _store_get_values(self, cr, uid, ids, fields, context):
3673 fncts = self.pool._store_function.get(self._name, [])
3674 for fnct in range(len(fncts)):
3679 for f in (fields or []):
3680 if f in fncts[fnct][3]:
3686 result.setdefault(fncts[fnct][0], {})
3688 # uid == 1 for accessing objects having rules defined on store fields
3689 ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3690 for id in filter(None, ids2):
3691 result[fncts[fnct][0]].setdefault(id, [])
3692 result[fncts[fnct][0]][id].append(fnct)
3694 for object in result:
3696 for id,fnct in result[object].items():
3697 k2.setdefault(tuple(fnct), [])
3698 k2[tuple(fnct)].append(id)
3699 for fnct,id in k2.items():
3700 dict.setdefault(fncts[fnct[0]][4],[])
3701 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3709 def _store_set_values(self, cr, uid, ids, fields, context):
3714 if self._log_access:
3715 cr.execute('select id,write_date from '+self._table+' where id IN %s',(tuple(ids),))
3719 field_dict.setdefault(r[0], [])
3720 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3721 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3722 for i in self.pool._store_function.get(self._name, []):
3724 up_write_date = write_date + datetime.timedelta(hours=i[5])
3725 if datetime.datetime.now() < up_write_date:
3727 field_dict[r[0]].append(i[1])
3733 if self._columns[f]._multi not in keys:
3734 keys.append(self._columns[f]._multi)
3735 todo.setdefault(self._columns[f]._multi, [])
3736 todo[self._columns[f]._multi].append(f)
3740 # uid == 1 for accessing objects having rules defined on store fields
3741 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3742 for id,value in result.items():
3744 for f in value.keys():
3745 if f in field_dict[id]:
3752 if self._columns[v]._type in ('many2one', 'one2one'):
3754 value[v] = value[v][0]
3757 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3758 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3761 cr.execute('update "' + self._table + '" set ' + \
3762 ','.join(upd0) + ' where id = %s', upd1)
3766 # uid == 1 for accessing objects having rules defined on store fields
3767 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3768 for r in result.keys():
3770 if r in field_dict.keys():
3771 if f in field_dict[r]:
3773 for id,value in result.items():
3774 if self._columns[f]._type in ('many2one', 'one2one'):
3779 cr.execute('update "' + self._table + '" set ' + \
3780 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3786 def perm_write(self, cr, user, ids, fields, context=None):
3787 raise NotImplementedError(_('This method does not exist anymore'))
3789 # TODO: ameliorer avec NULL
3790 def _where_calc(self, cr, user, args, active_test=True, context=None):
3791 """Computes the WHERE clause needed to implement an OpenERP domain.
3792 :param args: the domain to compute
3794 :param active_test: whether the default filtering of records with ``active``
3795 field set to ``False`` should be applied.
3796 :return: tuple with 3 elements: (where_clause, where_clause_params, tables) where
3797 ``where_clause`` contains a list of where clause elements (to be joined with 'AND'),
3798 ``where_clause_params`` is a list of parameters to be passed to the db layer
3799 for the where_clause expansion, and ``tables`` is the list of double-quoted
3800 table names that need to be included in the FROM clause.
3806 # if the object has a field named 'active', filter out all inactive
3807 # records unless they were explicitely asked for
3808 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3810 active_in_args = False
3812 if a[0] == 'active':
3813 active_in_args = True
3814 if not active_in_args:
3815 args.insert(0, ('active', '=', 1))
3817 args = [('active', '=', 1)]
3821 e = expression.expression(args)
3822 e.parse(cr, user, self, context)
3823 tables = e.get_tables()
3824 qu1, qu2 = e.to_sql()
3825 qu1 = qu1 and [qu1] or []
3827 qu1, qu2, tables = [], [], ['"%s"' % self._table]
3829 return (qu1, qu2, tables)
3831 def _check_qorder(self, word):
3832 if not regex_order.match(word):
3833 raise except_orm(_('AccessError'), _('Bad query.'))
3836 def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
3837 """Add what's missing in ``where_clause``, ``where_params``, ``tables`` to implement
3838 all appropriate ir.rules (on the current object but also from it's _inherits parents)
3840 :param where_clause: list with current elements of the WHERE clause (strings)
3841 :param where_clause_params: list with parameters for ``where_clause``
3842 :param tables: list with double-quoted names of the tables that are joined
3844 :param model_name: optional name of the model whose ir.rules should be applied (default:``self._name``)
3845 This could be useful for inheritance for example, but there is no provision to include
3846 the appropriate JOIN for linking the current model to the one referenced in model_name.
3847 :return: True if additional clauses where applied.
3849 added_clause, added_params, added_tables = self.pool.get('ir.rule').domain_get(cr, uid, model_name or self._name, mode, context=context)
3851 where_clause += added_clause
3852 where_clause_params += added_params
3853 for table in added_tables:
3854 if table not in tables:
3855 tables.append(table)
3859 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
3861 Private implementation of search() method, allowing specifying the uid to use for the access right check.
3862 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
3863 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
3864 This is ok at the security level because this method is private and not callable through XML-RPC.
3866 :param access_rights_uid: optional user ID to use when checking access rights
3867 (not for ir.rules, this is only for ir.model.access)
3871 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
3872 # compute the where, order by, limit and offset clauses
3873 (where_clause, where_clause_params, tables) = self._where_calc(cr, user, args, context=context)
3875 # apply direct ir.rules from current model
3876 self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', context=context)
3878 # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
3879 for inherited_model in self._inherits:
3880 previous_tables = list(tables)
3881 if self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
3882 # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
3883 # list of table in case the inherited table was not in the list before (as that means the corresponding
3884 # JOIN(s) was(were) not present)
3885 self._inherits_join_add(inherited_model, previous_tables, where_clause)
3886 tables = list(set(tables).union(set(previous_tables)))
3888 where = where_clause
3890 order_by = self._order
3892 self._check_qorder(order)
3893 o = order.split(' ')[0]
3894 if (o in self._columns):
3895 # we can only do efficient sort if the fields is stored in database
3896 if getattr(self._columns[o], '_classic_read'):
3898 elif (o in self._inherit_fields):
3899 parent_obj = self.pool.get(self._inherit_fields[o][0])
3900 if getattr(parent_obj._columns[o], '_classic_read'):
3901 # Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
3902 inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
3904 limit_str = limit and ' limit %d' % limit or ''
3905 offset_str = offset and ' offset %d' % offset or ''
3908 where_str = " WHERE %s" % " AND ".join(where)
3913 cr.execute('select count(%s.id) from ' % self._table +
3914 ','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
3917 cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params)
3919 return [x[0] for x in res]
3921 # returns the different values ever entered for one field
3922 # this is used, for example, in the client when the user hits enter on
3924 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3927 if field in self._inherit_fields:
3928 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3930 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3932 def copy_data(self, cr, uid, id, default=None, context=None):
3934 Copy given record's data with all its fields values
3936 :param cr: database cursor
3937 :param user: current user id
3938 :param id: id of the record to copy
3939 :param default: field values to override in the original values of the copied record
3940 :type default: dictionary
3941 :param context: context arguments, like lang, time zone
3942 :type context: dictionary
3943 :return: dictionary containing all the field values
3950 if 'state' not in default:
3951 if 'state' in self._defaults:
3952 if callable(self._defaults['state']):
3953 default['state'] = self._defaults['state'](self, cr, uid, context)
3955 default['state'] = self._defaults['state']
3957 context_wo_lang = context
3958 if 'lang' in context:
3959 del context_wo_lang['lang']
3960 data = self.read(cr, uid, [id], context=context_wo_lang)[0]
3962 fields = self.fields_get(cr, uid, context=context)
3964 ftype = fields[f]['type']
3966 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3970 data[f] = default[f]
3971 elif ftype == 'function':
3973 elif ftype == 'many2one':
3975 data[f] = data[f] and data[f][0]
3978 elif ftype in ('one2many', 'one2one'):
3980 rel = self.pool.get(fields[f]['relation'])
3982 # duplicate following the order of the ids
3983 # because we'll rely on it later for copying
3984 # translations in copy_translation()!
3986 for rel_id in data[f]:
3987 # the lines are first duplicated using the wrong (old)
3988 # parent but then are reassigned to the correct one thanks
3989 # to the (0, 0, ...)
3990 d = rel.copy_data(cr, uid, rel_id, context=context)
3991 res.append((0, 0, d))
3993 elif ftype == 'many2many':
3994 data[f] = [(6, 0, data[f])]
3998 # make sure we don't break the current parent_store structure and
3999 # force a clean recompute!
4000 for parent_column in ['parent_left', 'parent_right']:
4001 data.pop(parent_column, None)
4003 for v in self._inherits:
4004 del data[self._inherits[v]]
4007 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4008 trans_obj = self.pool.get('ir.translation')
4009 fields = self.fields_get(cr, uid, context=context)
4011 translation_records = []
4012 for field_name, field_def in fields.items():
4013 # we must recursively copy the translations for o2o and o2m
4014 if field_def['type'] in ('one2one', 'one2many'):
4015 target_obj = self.pool.get(field_def['relation'])
4016 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4017 # here we rely on the order of the ids to match the translations
4018 # as foreseen in copy_data()
4019 old_childs = sorted(old_record[field_name])
4020 new_childs = sorted(new_record[field_name])
4021 for (old_child, new_child) in zip(old_childs, new_childs):
4022 # recursive copy of translations here
4023 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4024 # and for translatable fields we keep them for copy
4025 elif field_def.get('translate'):
4027 if field_name in self._columns:
4028 trans_name = self._name + "," + field_name
4029 elif field_name in self._inherit_fields:
4030 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4032 trans_ids = trans_obj.search(cr, uid, [
4033 ('name', '=', trans_name),
4034 ('res_id','=', old_id)
4036 translation_records.extend(trans_obj.read(cr,uid,trans_ids,context=context))
4038 for record in translation_records:
4040 record['res_id'] = new_id
4041 trans_obj.create(cr, uid, record, context=context)
4044 def copy(self, cr, uid, id, default=None, context=None):
4046 Duplicate record with given id updating it with default values
4048 :param cr: database cursor
4049 :param uid: current user id
4050 :param id: id of the record to copy
4051 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4052 :type default: dictionary
4053 :param context: context arguments, like lang, time zone
4054 :type context: dictionary
4058 data = self.copy_data(cr, uid, id, default, context)
4059 new_id = self.create(cr, uid, data, context)
4060 self.copy_translations(cr, uid, id, new_id, context)
4063 def exists(self, cr, uid, ids, context=None):
4064 if type(ids) in (int,long):
4066 query = 'SELECT count(1) FROM "%s"' % (self._table)
4067 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4068 return cr.fetchone()[0] == len(ids)
4070 def check_recursion(self, cr, uid, ids, parent=None):
4072 Verifies that there is no loop in a hierarchical structure of records,
4073 by following the parent relationship using the **parent** field until a loop
4074 is detected or until a top-level record is found.
4076 :param cr: database cursor
4077 :param uid: current user id
4078 :param ids: list of ids of records to check
4079 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4080 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4084 parent = self._parent_name
4086 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4089 for i in range(0, len(ids), cr.IN_MAX):
4090 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4091 cr.execute(query, (tuple(sub_ids_parent),))
4092 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4093 ids_parent = ids_parent2
4094 for i in ids_parent:
4099 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4100 """Find out the XML ID of any database record, if there
4101 is one. This method works as a possible implementation
4102 for a function field, to be able to add it to any
4103 model object easily, referencing it as ``osv.osv.get_xml_id``.
4105 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4107 :return: the fully qualified XML ID of the given object,
4108 defaulting to an empty string when there's none
4109 (to be usable as a function field).
4111 result = dict.fromkeys(ids, '')
4112 model_data_obj = self.pool.get('ir.model.data')
4113 data_ids = model_data_obj.search(cr,uid,
4114 [('model','=',self._name),('res_id','in',ids)])
4115 data_results = model_data_obj.read(cr,uid,data_ids,
4116 ['name','module','res_id'])
4117 for record in data_results:
4118 result[record['res_id']] = '%(module)s.%(name)s' % record
4121 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: