1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 # Object relationnal mapping to postgresql module
24 # . Hierarchical structure
25 # . Constraints consistency, validations
26 # . Object meta Data depends on its status
27 # . Optimised processing by complex query (multiple actions at once)
28 # . Default fields value
29 # . Permissions optimisation
30 # . Persistant object: DB postgresql
32 # . Multi-level caching system
33 # . 2 different inheritancies
35 # - classicals (varchar, integer, boolean, ...)
36 # - relations (one2many, many2one, many2many)
53 from lxml import etree
54 from tools.config import config
55 from tools.translate import _
59 from tools.safe_eval import safe_eval as eval
61 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
64 POSTGRES_CONFDELTYPES = {
72 def last_day_of_current_month():
73 today = datetime.date.today()
74 last_day = str(calendar.monthrange(today.year, today.month)[1])
75 return time.strftime('%Y-%m-' + last_day)
77 def intersect(la, lb):
78 return filter(lambda x: x in lb, la)
80 class except_orm(Exception):
81 def __init__(self, name, value):
84 self.args = (name, value)
86 class BrowseRecordError(Exception):
89 # Readonly python database object browser
90 class browse_null(object):
95 def __getitem__(self, name):
98 def __getattr__(self, name):
99 return None # XXX: return self ?
107 def __nonzero__(self):
110 def __unicode__(self):
115 # TODO: execute an object method on browse_record_list
117 class browse_record_list(list):
119 def __init__(self, lst, context=None):
122 super(browse_record_list, self).__init__(lst)
123 self.context = context
126 class browse_record(object):
127 logger = netsvc.Logger()
129 def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}):
131 table : the object (inherited from orm)
132 context : dictionary with an optional context
136 self._list_class = list_class or browse_record_list
141 self._table_name = self._table._name
142 self.__logger = logging.getLogger(
143 'osv.browse_record.' + self._table_name)
144 self._context = context
145 self._fields_process = fields_process
147 cache.setdefault(table._name, {})
148 self._data = cache[table._name]
150 if not (id and isinstance(id, (int, long,))):
151 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
152 # if not table.exists(cr, uid, id, context):
153 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
155 if id not in self._data:
156 self._data[id] = {'id': id}
160 def __getitem__(self, name):
164 if name not in self._data[self._id]:
165 # build the list of fields we will fetch
167 # fetch the definition of the field which was asked for
168 if name in self._table._columns:
169 col = self._table._columns[name]
170 elif name in self._table._inherit_fields:
171 col = self._table._inherit_fields[name][2]
172 elif hasattr(self._table, str(name)):
173 attr = getattr(self._table, name)
175 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
176 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
180 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
181 "Field '%s' does not exist in object '%s': \n%s" % (
182 name, self, ''.join(traceback.format_exc())))
183 raise KeyError("Field '%s' does not exist in object '%s'" % (
186 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
188 # gen the list of "local" (ie not inherited) fields which are classic or many2one
189 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
190 # gen the list of inherited fields
191 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
192 # complete the field list with the inherited fields which are classic or many2one
193 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
194 # otherwise we fetch only that field
196 fields_to_fetch = [(name, col)]
197 ids = filter(lambda id: name not in self._data[id], self._data.keys())
199 field_names = map(lambda x: x[0], fields_to_fetch)
200 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
201 if self._fields_process:
202 lang = self._context.get('lang', 'en_US') or 'en_US'
203 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid,[('code','=',lang)])
205 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
206 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid,lang_obj_ids[0])
208 for field_name, field_column in fields_to_fetch:
209 if field_column._type in self._fields_process:
210 for result_line in field_values:
211 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
212 if result_line[field_name]:
213 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
216 # Where did those ids come from? Perhaps old entries in ir_model_dat?
217 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
218 raise KeyError('Field %s not found in %s'%(name,self))
219 # create browse records for 'remote' objects
220 for result_line in field_values:
222 for field_name, field_column in fields_to_fetch:
223 if field_column._type in ('many2one', 'one2one'):
224 if result_line[field_name]:
225 obj = self._table.pool.get(field_column._obj)
226 if isinstance(result_line[field_name], (list,tuple)):
227 value = result_line[field_name][0]
229 value = result_line[field_name]
231 # FIXME: this happen when a _inherits object
232 # overwrite a field of it parent. Need
233 # testing to be sure we got the right
234 # object and not the parent one.
235 if not isinstance(value, browse_record):
236 new_data[field_name] = browse_record(self._cr,
237 self._uid, value, obj, self._cache,
238 context=self._context,
239 list_class=self._list_class,
240 fields_process=self._fields_process)
242 new_data[field_name] = value
244 new_data[field_name] = browse_null()
246 new_data[field_name] = browse_null()
247 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
248 new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(field_column._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
249 elif field_column._type in ('reference'):
250 if result_line[field_name]:
251 if isinstance(result_line[field_name], browse_record):
252 new_data[field_name] = result_line[field_name]
254 ref_obj, ref_id = result_line[field_name].split(',')
255 ref_id = long(ref_id)
256 obj = self._table.pool.get(ref_obj)
257 new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
259 new_data[field_name] = browse_null()
261 new_data[field_name] = result_line[field_name]
262 self._data[result_line['id']].update(new_data)
264 if not name in self._data[self._id]:
265 #how did this happen?
266 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
267 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
268 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
269 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
270 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
271 return self._data[self._id][name]
273 def __getattr__(self, name):
277 raise AttributeError(e)
279 def __contains__(self, name):
280 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
282 def __hasattr__(self, name):
289 return "browse_record(%s, %d)" % (self._table_name, self._id)
291 def __eq__(self, other):
292 if not isinstance(other, browse_record):
294 return (self._table_name, self._id) == (other._table_name, other._id)
296 def __ne__(self, other):
297 if not isinstance(other, browse_record):
299 return (self._table_name, self._id) != (other._table_name, other._id)
301 # we need to define __unicode__ even though we've already defined __str__
302 # because we have overridden __getattr__
303 def __unicode__(self):
304 return unicode(str(self))
307 return hash((self._table_name, self._id))
315 (type returned by postgres when the column was created, type expression to create the column)
319 fields.boolean: 'bool',
320 fields.integer: 'int4',
321 fields.integer_big: 'int8',
325 fields.datetime: 'timestamp',
326 fields.binary: 'bytea',
327 fields.many2one: 'int4',
329 if type(f) in type_dict:
330 f_type = (type_dict[type(f)], type_dict[type(f)])
331 elif isinstance(f, fields.float):
333 f_type = ('numeric', 'NUMERIC')
335 f_type = ('float8', 'DOUBLE PRECISION')
336 elif isinstance(f, (fields.char, fields.reference)):
337 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
338 elif isinstance(f, fields.selection):
339 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
340 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
341 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
344 f_size = getattr(f, 'size', None) or 16
347 f_type = ('int4', 'INTEGER')
349 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
350 elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict:
351 t = eval('fields.'+(f._type), globals())
352 f_type = (type_dict[t], type_dict[t])
353 elif isinstance(f, fields.function) and f._type == 'float':
355 f_type = ('numeric', 'NUMERIC')
357 f_type = ('float8', 'DOUBLE PRECISION')
358 elif isinstance(f, fields.function) and f._type == 'selection':
359 f_type = ('text', 'text')
360 elif isinstance(f, fields.function) and f._type == 'char':
361 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
363 logger = netsvc.Logger()
364 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
369 class orm_template(object):
375 _parent_name = 'parent_id'
376 _parent_store = False
377 _parent_order = False
387 CONCURRENCY_CHECK_FIELD = '__last_update'
388 def log(self, cr, uid, id, message, secondary=False, context=None):
389 return self.pool.get('res.log').create(cr, uid, {
391 'res_model': self._name,
392 'secondary': secondary,
397 def view_init(self, cr , uid , fields_list, context=None):
398 """Override this method to do specific things when a view on the object is opened."""
401 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
402 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
404 def _field_create(self, cr, context={}):
405 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
407 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
408 model_id = cr.fetchone()[0]
409 cr.execute("INSERT INTO ir_model (id,model, name, info,state) VALUES (%s, %s, %s, %s, %s)", (model_id, self._name, self._description, self.__doc__, 'base'))
411 model_id = cr.fetchone()[0]
412 if 'module' in context:
413 name_id = 'model_'+self._name.replace('.','_')
414 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module']))
416 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
417 (name_id, context['module'], 'ir.model', model_id)
422 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
424 for rec in cr.dictfetchall():
425 cols[rec['name']] = rec
427 for (k, f) in self._columns.items():
429 'model_id': model_id,
432 'field_description': f.string.replace("'", " "),
434 'relation': f._obj or '',
435 'view_load': (f.view_load and 1) or 0,
436 'select_level': tools.ustr(f.select or 0),
437 'readonly':(f.readonly and 1) or 0,
438 'required':(f.required and 1) or 0,
439 'selectable' : (f.selectable and 1) or 0,
440 'relation_field': (f._type=='one2many' and isinstance(f,fields.one2many)) and f._fields_id or '',
442 # When its a custom field,it does not contain f.select
443 if context.get('field_state','base') == 'manual':
444 if context.get('field_name','') == k:
445 vals['select_level'] = context.get('select','0')
446 #setting value to let the problem NOT occur next time
448 vals['select_level'] = cols[k]['select_level']
451 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
452 id = cr.fetchone()[0]
454 cr.execute("""INSERT INTO ir_model_fields (
455 id, model_id, model, name, field_description, ttype,
456 relation,view_load,state,select_level,relation_field
458 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
460 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
461 vals['relation'], bool(vals['view_load']), 'base',
462 vals['select_level'],vals['relation_field']
464 if 'module' in context:
465 name1 = 'field_' + self._table + '_' + k
466 cr.execute("select name from ir_model_data where name=%s", (name1,))
468 name1 = name1 + "_" + str(id)
469 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
470 (name1, context['module'], 'ir.model.fields', id)
473 for key, val in vals.items():
474 if cols[k][key] != vals[key]:
475 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
477 cr.execute("""UPDATE ir_model_fields SET
478 model_id=%s, field_description=%s, ttype=%s, relation=%s,
479 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
481 model=%s AND name=%s""", (
482 vals['model_id'], vals['field_description'], vals['ttype'],
483 vals['relation'], bool(vals['view_load']),
484 vals['select_level'], bool(vals['readonly']),bool(vals['required']),bool(vals['selectable']),vals['relation_field'],vals['model'], vals['name']
489 def _auto_init(self, cr, context={}):
490 self._field_create(cr, context)
492 def __init__(self, cr):
493 if not self._name and not hasattr(self, '_inherit'):
494 name = type(self).__name__.split('.')[0]
495 msg = "The class %s has to have a _name attribute" % name
497 logger = netsvc.Logger()
498 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
499 raise except_orm('ValueError', msg )
501 if not self._description:
502 self._description = self._name
504 self._table = self._name.replace('.', '_')
506 def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
508 Fetch records as objects allowing to use dot notation to browse fields and relations
510 :param cr: database cursor
511 :param user: current user id
512 :param select: id or list of ids
513 :param context: context arguments, like lang, time zone
514 :rtype: object or list of objects requested
519 self._list_class = list_class or browse_record_list
521 # need to accepts ints and longs because ids coming from a method
522 # launched by button in the interface have a type long...
523 if isinstance(select, (int, long)):
524 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
525 elif isinstance(select, list):
526 return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context)
530 def __export_row(self, cr, uid, row, fields, context=None):
534 def check_type(field_type):
535 if field_type == 'float':
537 elif field_type == 'integer':
539 elif field_type == 'boolean':
543 def selection_field(in_field):
544 col_obj = self.pool.get(in_field.keys()[0])
545 if f[i] in col_obj._columns.keys():
546 return col_obj._columns[f[i]]
547 elif f[i] in col_obj._inherits.keys():
548 selection_field(col_obj._inherits)
554 data = map(lambda x: '', range(len(fields)))
556 for fpos in range(len(fields)):
565 model_data = self.pool.get('ir.model.data')
566 data_ids = model_data.search(cr, uid, [('model','=',r._table_name),('res_id','=',r['id'])])
568 d = model_data.read(cr, uid, data_ids, ['name','module'])[0]
570 r = '%s.%s'%(d['module'],d['name'])
577 # To display external name of selection field when its exported
578 if not context.get('import_comp',False):# Allow external name only if its not import compatible
580 if f[i] in self._columns.keys():
581 cols = self._columns[f[i]]
582 elif f[i] in self._inherit_fields.keys():
583 cols = selection_field(self._inherits)
584 if cols and cols._type == 'selection':
585 sel_list = cols.selection
586 if r and type(sel_list) == type([]):
587 r = [x[1] for x in sel_list if r==x[0]]
588 r = r and r[0] or False
590 if f[i] in self._columns:
591 r = check_type(self._columns[f[i]]._type)
592 elif f[i] in self._inherit_fields:
593 r = check_type(self._inherit_fields[f[i]][2]._type)
596 if isinstance(r, (browse_record_list, list)):
598 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
604 lines2 = self.__export_row(cr, uid, row2, fields2,
607 for fpos2 in range(len(fields)):
608 if lines2 and lines2[0][fpos2]:
609 data[fpos2] = lines2[0][fpos2]
613 if isinstance(rr.name, browse_record):
615 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
616 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
617 dt += tools.ustr(rr_name or '') + ','
627 if isinstance(r, browse_record):
628 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
629 r = r and r[0] and r[0][1] or ''
630 data[fpos] = tools.ustr(r or '')
631 return [data] + lines
633 def export_data(self, cr, uid, ids, fields_to_export, context=None):
635 Export fields for selected objects
637 :param cr: database cursor
638 :param uid: current user id
639 :param ids: list of ids
640 :param fields_to_export: list of fields
641 :param context: context arguments, like lang, time zone, may contain import_comp(default: False) to make exported data compatible with import_data()
642 :rtype: dictionary with a *datas* matrix
644 This method is used when exporting data via client menu
649 imp_comp = context.get('import_comp',False)
650 cols = self._columns.copy()
651 for f in self._inherit_fields:
652 cols.update({f: self._inherit_fields[f][2]})
653 fields_to_export = map(lambda x: x.split('/'), fields_to_export)
654 fields_export = fields_to_export+[]
657 for field in fields_export:
658 if imp_comp and len(field)>1:
659 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
660 elif len (field) <=1:
661 if imp_comp and cols.get(field and field[0],False):
662 if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
663 or isinstance(cols[field[0]], fields.related)\
664 or isinstance(cols[field[0]], fields.one2many)):
665 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
667 if imp_comp and len(warning_fields):
668 warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' %('\n'.join(warning_fields))
670 return {'warning' : warning}
671 for row in self.browse(cr, uid, ids, context):
672 datas += self.__export_row(cr, uid, row, fields_to_export, context)
673 return {'datas':datas}
675 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
677 Import given data in given module
679 :param cr: database cursor
680 :param uid: current user id
681 :param ids: list of ids
682 :param fields: list of fields
683 :param data: data to import
684 :param mode: 'init' or 'update' for record creation
685 :param current_module: module name
686 :param noupdate: flag for record creation
687 :param context: context arguments, like lang, time zone,
688 :param filename: optional file to store partial import state for recovery
691 This method is used when importing data via client menu
696 fields = map(lambda x: x.split('/'), fields)
697 logger = netsvc.Logger()
698 ir_model_data_obj = self.pool.get('ir.model.data')
700 def _check_db_id(self, model_name, db_id):
701 obj_model = self.pool.get(model_name)
702 ids = obj_model.search(cr, uid, [('id','=',int(db_id))])
704 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
707 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
708 line = datas[position]
717 ir_model_data_obj = self.pool.get('ir.model.data')
719 # Import normal fields
721 for i in range(len(fields)):
723 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
728 if prefix and not prefix[0] in field:
731 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
735 field_name = field[0].split(':')[0]
736 model_rel = fields_def[field_name]['relation']
738 if fields_def[field[len(prefix)][:-6]]['type']=='many2many':
740 for db_id in line[i].split(config.get('csv_internal_sep')):
742 _check_db_id(self, model_rel, db_id)
745 warning += [tools.exception_to_unicode(e)]
746 logger.notifyChannel("import", netsvc.LOG_ERROR,
747 tools.exception_to_unicode(e))
749 res = [(6, 0, res_id)]
752 _check_db_id(self, model_rel, line[i])
755 warning += [tools.exception_to_unicode(e)]
756 logger.notifyChannel("import", netsvc.LOG_ERROR,
757 tools.exception_to_unicode(e))
758 row[field_name] = res or False
761 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
764 if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
766 for word in line[i].split(config.get('csv_internal_sep')):
768 module, xml_id = word.rsplit('.', 1)
770 module, xml_id = current_module, word
771 id = ir_model_data_obj._get_id(cr, uid, module,
773 res_id2 = ir_model_data_obj.read(cr, uid, [id],
774 ['res_id'])[0]['res_id']
776 res_id.append(res_id2)
778 res_id = [(6, 0, res_id)]
781 module, xml_id = line[i].rsplit('.', 1)
783 module, xml_id = current_module, line[i]
784 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
785 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
787 res_id = ir_model_data[0]['res_id']
789 raise ValueError('No references to %s.%s' % (module, xml_id))
790 row[field[-1][:-3]] = res_id or False
792 if (len(field) == len(prefix)+1) and \
793 len(field[len(prefix)].split(':lang=')) == 2:
794 f, lang = field[len(prefix)].split(':lang=')
795 translate.setdefault(lang, {})[f]=line[i] or False
797 if (len(field) == len(prefix)+1) and \
798 (prefix == field[0:len(prefix)]):
799 if field[len(prefix)] == "id":
802 is_xml_id = data_id = line[i]
803 d = data_id.split('.')
804 module = len(d)>1 and d[0] or ''
805 name = len(d)>1 and d[1] or d[0]
806 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('name','=',name)])
808 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
810 if is_db_id and not db_id:
811 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('res_id','=',is_db_id)])
812 if not len(data_ids):
813 ir_model_data_obj.create(cr, uid, {'module':module, 'model':model_name, 'name':name, 'res_id':is_db_id})
815 if is_db_id and int(db_id) != int(is_db_id):
816 warning += [_("Id is not the same than existing one: %s")%(is_db_id)]
817 logger.notifyChannel("import", netsvc.LOG_ERROR,
818 _("Id is not the same than existing one: %s")%(is_db_id))
821 if field[len(prefix)] == "db_id":
824 _check_db_id(self, model_name, line[i])
825 data_res_id = is_db_id = int(line[i])
827 warning += [tools.exception_to_unicode(e)]
828 logger.notifyChannel("import", netsvc.LOG_ERROR,
829 tools.exception_to_unicode(e))
831 data_ids = ir_model_data_obj.search(cr, uid, [('model','=',model_name),('res_id','=',line[i])])
833 d = ir_model_data_obj.read(cr, uid, data_ids, ['name','module'])[0]
836 data_id = '%s.%s'%(d['module'],d['name'])
839 if is_xml_id and not data_id:
841 if is_xml_id and is_xml_id!=data_id:
842 warning += [_("Id is not the same than existing one: %s")%(line[i])]
843 logger.notifyChannel("import", netsvc.LOG_ERROR,
844 _("Id is not the same than existing one: %s")%(line[i]))
847 if fields_def[field[len(prefix)]]['type'] == 'integer':
848 res = line[i] and int(line[i])
849 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
850 res = line[i].lower() not in ('0', 'false', 'off')
851 elif fields_def[field[len(prefix)]]['type'] == 'float':
852 res = line[i] and float(line[i])
853 elif fields_def[field[len(prefix)]]['type'] == 'selection':
855 if isinstance(fields_def[field[len(prefix)]]['selection'],
857 sel = fields_def[field[len(prefix)]]['selection']
859 sel = fields_def[field[len(prefix)]]['selection'](self,
862 if line[i] in [tools.ustr(key),tools.ustr(val)]: #Acepting key or value for selection field
865 if line[i] and not res:
866 logger.notifyChannel("import", netsvc.LOG_WARNING,
867 _("key '%s' not found in selection field '%s'") % \
868 (line[i], field[len(prefix)]))
870 warning += [_("Key/value '%s' not found in selection field '%s'")%(line[i],field[len(prefix)])]
872 elif fields_def[field[len(prefix)]]['type']=='many2one':
875 relation = fields_def[field[len(prefix)]]['relation']
876 res2 = self.pool.get(relation).name_search(cr, uid,
877 line[i], [], operator='=', context=context)
878 res = (res2 and res2[0][0]) or False
880 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
881 logger.notifyChannel("import", netsvc.LOG_WARNING,
882 _("Relation not found: %s on '%s'")%(line[i],relation))
883 elif fields_def[field[len(prefix)]]['type']=='many2many':
886 relation = fields_def[field[len(prefix)]]['relation']
887 for word in line[i].split(config.get('csv_internal_sep')):
888 res2 = self.pool.get(relation).name_search(cr,
889 uid, word, [], operator='=', context=context)
890 res3 = (res2 and res2[0][0]) or False
892 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
893 logger.notifyChannel("import",
895 _("Relation not found: %s on '%s'")%(line[i],relation))
901 res = line[i] or False
902 row[field[len(prefix)]] = res
903 elif (prefix==field[0:len(prefix)]):
904 if field[0] not in todo:
905 todo.append(field[len(prefix)])
907 # Import one2many, many2many fields
911 relation_obj = self.pool.get(fields_def[field]['relation'])
912 newfd = relation_obj.fields_get(
913 cr, uid, context=context)
914 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
915 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
916 nbrmax = max(nbrmax, max2)
917 warning = warning + w2
918 reduce(lambda x, y: x and y, newrow)
919 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
920 [(0, 0, newrow)]) or []
922 while (position+i)<len(datas):
924 for j in range(len(fields)):
926 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
931 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
932 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
934 if reduce(lambda x, y: x or y, newrow.values()):
935 row[field].append((0, 0, newrow))
937 nbrmax = max(nbrmax, i)
940 for i in range(max(nbrmax, 1)):
943 result = (row, nbrmax, warning, translate, data_id, data_res_id)
946 fields_def = self.fields_get(cr, uid, context=context)
949 initial_size = len(datas)
950 if config.get('import_partial', False) and filename:
951 data = pickle.load(file(config.get('import_partial')))
952 original_value = data.get(filename, 0)
958 (res, other, warning, translate, data_id, res_id) = \
959 process_liness(self, datas, [], current_module, self._name, fields_def)
962 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
965 id = ir_model_data_obj._update(cr, uid, self._name,
966 current_module, res, xml_id=data_id, mode=mode,
967 noupdate=noupdate, res_id=res_id, context=context)
972 if isinstance(e,psycopg2.IntegrityError):
973 msg= _('Insertion Failed! ')
974 for key in self.pool._sql_error.keys():
976 msg = self.pool._sql_error[key]
978 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
979 if isinstance(e, osv.orm.except_orm ):
980 msg = _('Insertion Failed! ' + e[1])
981 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
982 #Raising Uncaught exception
983 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '' )
985 for lang in translate:
986 context2 = context.copy()
987 context2['lang'] = lang
988 self.write(cr, uid, [id], translate[lang], context2)
989 if config.get('import_partial', False) and filename and (not (counter%100)) :
990 data = pickle.load(file(config.get('import_partial')))
991 data[filename] = initial_size - len(datas) + original_value
992 pickle.dump(data, file(config.get('import_partial'),'wb'))
993 if context.get('defer_parent_store_computation'):
994 self._parent_store_compute(cr)
997 #except Exception, e:
998 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1001 # return (-1, res, e[0], warning)
1003 # return (-1, res, e[0], '')
1006 # TODO: Send a request with the result and multi-thread !
1008 if context.get('defer_parent_store_computation'):
1009 self._parent_store_compute(cr)
1010 return (done, 0, 0, 0)
1012 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1013 raise NotImplementedError(_('The read method is not implemented on this object !'))
1015 def get_invalid_fields(self,cr,uid):
1016 return list(self._invalids)
1018 def _validate(self, cr, uid, ids, context=None):
1019 context = context or {}
1020 lng = context.get('lang', False) or 'en_US'
1021 trans = self.pool.get('ir.translation')
1023 for constraint in self._constraints:
1024 fun, msg, fields = constraint
1025 if not fun(self, cr, uid, ids):
1026 # Check presence of __call__ directly instead of using
1027 # callable() because it will be deprecated as of Python 3.0
1028 if hasattr(msg, '__call__'):
1029 txt_msg, params = msg(self, cr, uid, ids)
1030 tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg
1031 translated_msg = tmp_msg % params
1033 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1035 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1037 self._invalids.update(fields)
1040 raise except_orm('ValidateError', '\n'.join(error_msgs))
1042 self._invalids.clear()
1044 def default_get(self, cr, uid, fields_list, context=None):
1046 Returns default values for the fields in fields_list.
1048 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1049 :type fields_list: list
1050 :param context: usual context dictionary - it may contains keys in the form ``default_XXX``,
1051 where XXX is a field name to set or override a default value.
1052 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1054 # trigger view init hook
1055 self.view_init(cr, uid, fields_list, context)
1061 # get the default values for the inherited fields
1062 for t in self._inherits.keys():
1063 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1066 # get the default values defined in the object
1067 for f in fields_list:
1068 if f in self._defaults:
1069 if callable(self._defaults[f]):
1070 defaults[f] = self._defaults[f](self, cr, uid, context)
1072 defaults[f] = self._defaults[f]
1074 fld_def = ((f in self._columns) and self._columns[f]) \
1075 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1078 if isinstance(fld_def, fields.property):
1079 property_obj = self.pool.get('ir.property')
1080 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1082 if isinstance(prop_value, (browse_record, browse_null)):
1083 defaults[f] = prop_value.id
1085 defaults[f] = prop_value
1087 if f not in defaults:
1090 # get the default values set by the user and override the default
1091 # values defined in the object
1092 ir_values_obj = self.pool.get('ir.values')
1093 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1094 for id, field, field_value in res:
1095 if field in fields_list:
1096 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1097 if fld_def._type in ('many2one', 'one2one'):
1098 obj = self.pool.get(fld_def._obj)
1099 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1101 if fld_def._type in ('many2many'):
1102 obj = self.pool.get(fld_def._obj)
1104 for i in range(len(field_value)):
1105 if not obj.search(cr, uid, [('id', '=',
1108 field_value2.append(field_value[i])
1109 field_value = field_value2
1110 if fld_def._type in ('one2many'):
1111 obj = self.pool.get(fld_def._obj)
1113 for i in range(len(field_value)):
1114 field_value2.append({})
1115 for field2 in field_value[i]:
1116 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1117 obj2 = self.pool.get(obj._columns[field2]._obj)
1118 if not obj2.search(cr, uid,
1119 [('id', '=', field_value[i][field2])]):
1121 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1122 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1123 if not obj2.search(cr, uid,
1124 [('id', '=', field_value[i][field2])]):
1126 # TODO add test for many2many and one2many
1127 field_value2[i][field2] = field_value[i][field2]
1128 field_value = field_value2
1129 defaults[field] = field_value
1131 # get the default values from the context
1132 for key in context or {}:
1133 if key.startswith('default_') and (key[8:] in fields_list):
1134 defaults[key[8:]] = context[key]
1138 def perm_read(self, cr, user, ids, context=None, details=True):
1139 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1141 def unlink(self, cr, uid, ids, context=None):
1142 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1144 def write(self, cr, user, ids, vals, context=None):
1145 raise NotImplementedError(_('The write method is not implemented on this object !'))
1147 def create(self, cr, user, vals, context=None):
1148 raise NotImplementedError(_('The create method is not implemented on this object !'))
1150 def fields_get_keys(self, cr, user, context=None):
1151 res = self._columns.keys()
1152 for parent in self._inherits:
1153 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1156 # returns the definition of each field in the object
1157 # the optional fields parameter can limit the result to some fields
1158 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1162 translation_obj = self.pool.get('ir.translation')
1163 for parent in self._inherits:
1164 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1166 if self._columns.keys():
1167 for f in self._columns.keys():
1168 if allfields and f not in allfields:
1170 res[f] = {'type': self._columns[f]._type}
1171 # This additional attributes for M2M and function field is added
1172 # because we need to display tooltip with this additional information
1173 # when client is started in debug mode.
1174 if isinstance(self._columns[f], fields.function):
1175 res[f]['function'] = self._columns[f]._fnct and self._columns[f]._fnct.func_name or False
1176 res[f]['store'] = self._columns[f].store
1177 if isinstance(self._columns[f].store, dict):
1178 res[f]['store'] = str(self._columns[f].store)
1179 res[f]['fnct_search'] = self._columns[f]._fnct_search and self._columns[f]._fnct_search.func_name or False
1180 res[f]['fnct_inv'] = self._columns[f]._fnct_inv and self._columns[f]._fnct_inv.func_name or False
1181 res[f]['fnct_inv_arg'] = self._columns[f]._fnct_inv_arg or False
1182 res[f]['func_obj'] = self._columns[f]._obj or False
1183 res[f]['func_method'] = self._columns[f]._method
1184 if isinstance(self._columns[f], fields.many2many):
1185 res[f]['related_columns'] = list((self._columns[f]._id1, self._columns[f]._id2))
1186 res[f]['third_table'] = self._columns[f]._rel
1187 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1188 'change_default', 'translate', 'help', 'select', 'selectable'):
1189 if getattr(self._columns[f], arg):
1190 res[f][arg] = getattr(self._columns[f], arg)
1191 if not write_access:
1192 res[f]['readonly'] = True
1193 res[f]['states'] = {}
1194 for arg in ('digits', 'invisible','filters'):
1195 if getattr(self._columns[f], arg, None):
1196 res[f][arg] = getattr(self._columns[f], arg)
1198 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', self._columns[f].string)
1200 res[f]['string'] = res_trans
1201 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1203 res[f]['help'] = help_trans
1205 if hasattr(self._columns[f], 'selection'):
1206 if isinstance(self._columns[f].selection, (tuple, list)):
1207 sel = self._columns[f].selection
1208 # translate each selection option
1210 for (key, val) in sel:
1213 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1214 sel2.append((key, val2 or val))
1216 res[f]['selection'] = sel
1218 # call the 'dynamic selection' function
1219 res[f]['selection'] = self._columns[f].selection(self, cr,
1221 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1222 res[f]['relation'] = self._columns[f]._obj
1223 res[f]['domain'] = self._columns[f]._domain
1224 res[f]['context'] = self._columns[f]._context
1226 #TODO : read the fields from the database
1230 # filter out fields which aren't in the fields list
1231 for r in res.keys():
1232 if r not in allfields:
1237 # Overload this method if you need a window title which depends on the context
1239 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1242 def __view_look_dom(self, cr, user, node, view_id, context=None):
1250 if isinstance(s, unicode):
1251 return s.encode('utf8')
1254 # return True if node can be displayed to current user
1255 def check_group(node):
1256 if node.get('groups'):
1257 groups = node.get('groups').split(',')
1258 access_pool = self.pool.get('ir.model.access')
1259 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1261 node.set('invisible', '1')
1262 if 'attrs' in node.attrib:
1263 del(node.attrib['attrs']) #avoid making field visible later
1264 del(node.attrib['groups'])
1269 if node.tag in ('field', 'node', 'arrow'):
1270 if node.get('object'):
1275 if f.tag in ('field'):
1276 xml += etree.tostring(f, encoding="utf-8")
1278 new_xml = etree.fromstring(encode(xml))
1279 ctx = context.copy()
1280 ctx['base_model_name'] = self._name
1281 xarch, xfields = self.pool.get(node.get('object',False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1282 views[str(f.tag)] = {
1286 attrs = {'views': views}
1288 fields = views.get('field',False) and views['field'].get('fields',False)
1289 if node.get('name'):
1292 if node.get('name') in self._columns:
1293 column = self._columns[node.get('name')]
1295 column = self._inherit_fields[node.get('name')][2]
1300 relation = self.pool.get(column._obj)
1305 if f.tag in ('form', 'tree', 'graph'):
1307 ctx = context.copy()
1308 ctx['base_model_name'] = self._name
1309 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1310 views[str(f.tag)] = {
1314 attrs = {'views': views}
1315 if node.get('widget') and node.get('widget') == 'selection':
1316 if not check_group(node):
1317 name = node.get('name')
1318 default = self.default_get(cr, user, [name], context=context).get(name)
1320 attrs['selection'] = relation.name_get(cr, 1, [default], context=context)
1322 attrs['selection'] = []
1323 # We can not use the 'string' domain has it is defined according to the record !
1325 # If domain and context are strings, we keep them for client-side, otherwise
1326 # we evaluate them server-side to consider them when generating the list of
1328 # TODO: find a way to remove this hack, by allow dynamic domains
1330 if column._domain and not isinstance(column._domain, basestring):
1331 dom = column._domain
1332 dom += eval(node.get('domain','[]'), {'uid':user, 'time':time})
1333 search_context = dict(context)
1334 if column._context and not isinstance(column._context, basestring):
1335 search_context.update(column._context)
1336 attrs['selection'] = relation._name_search(cr, 1, '', dom, context=search_context, limit=None, name_get_uid=1)
1337 if (node.get('required') and not int(node.get('required'))) or not column.required:
1338 attrs['selection'].append((False,''))
1339 fields[node.get('name')] = attrs
1341 elif node.tag in ('form', 'tree'):
1342 result = self.view_header_get(cr, user, False, node.tag, context)
1344 node.set('string', result)
1346 elif node.tag == 'calendar':
1347 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1348 if node.get(additional_field):
1349 fields[node.get(additional_field)] = {}
1351 if 'groups' in node.attrib:
1355 if ('lang' in context) and not result:
1356 if node.get('string'):
1357 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string').encode('utf8'))
1358 if not trans and ('base_model_name' in context):
1359 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string').encode('utf8'))
1361 node.set('string', trans)
1363 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum').encode('utf8'))
1365 node.set('sum', trans)
1369 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1373 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1374 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1376 rolesobj = self.pool.get('res.roles')
1377 usersobj = self.pool.get('res.users')
1379 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1380 for button in buttons:
1382 if user != 1: # admin user has all roles
1383 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
1384 # TODO handle the case of more than one workflow for a model
1385 cr.execute("""SELECT DISTINCT t.role_id
1387 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1388 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1391 """, (self._name, button.get('name'),))
1392 roles = cr.fetchall()
1394 # draft -> valid = signal_next (role X)
1395 # draft -> cancel = signal_cancel (no role)
1397 # valid -> running = signal_next (role Y)
1398 # valid -> cancel = signal_cancel (role Z)
1400 # running -> done = signal_next (role Z)
1401 # running -> cancel = signal_cancel (role Z)
1403 # As we don't know the object state, in this scenario,
1404 # the button "signal_cancel" will be always shown as there is no restriction to cancel in draft
1405 # the button "signal_next" will be show if the user has any of the roles (X Y or Z)
1406 # The verification will be made later in workflow process...
1408 can_click = any((not role) or rolesobj.check(cr, user, user_roles, role) for (role,) in roles)
1410 button.set('readonly', str(int(not can_click)))
1412 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1415 if node.tag=='diagram':
1416 if node.getchildren()[0].tag=='node':
1417 node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1418 if node.getchildren()[1].tag=='arrow':
1419 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1420 for key,value in node_fields.items():
1422 for key,value in arrow_fields.items():
1425 fields = self.fields_get(cr, user, fields_def.keys(), context)
1426 for field in fields_def:
1428 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1429 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1430 elif field in fields:
1431 fields[field].update(fields_def[field])
1433 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))
1434 res = cr.fetchall()[:]
1436 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1437 msg = "\n * ".join([r[0] for r in res])
1438 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1439 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1440 raise except_orm('View error', msg)
1443 def __get_default_calendar_view(self):
1444 """Generate a default calendar view (For internal use only).
1447 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1448 '<calendar string="%s"') % (self._description)
1450 if (self._date_name not in self._columns):
1452 for dt in ['date','date_start','x_date','x_date_start']:
1453 if dt in self._columns:
1454 self._date_name = dt
1459 raise except_orm(_('Invalid Object Architecture!'),_("Insufficient fields for Calendar View!"))
1462 arch +=' date_start="%s"' % (self._date_name)
1464 for color in ["user_id","partner_id","x_user_id","x_partner_id"]:
1465 if color in self._columns:
1466 arch += ' color="' + color + '"'
1469 dt_stop_flag = False
1471 for dt_stop in ["date_stop","date_end","x_date_stop","x_date_end"]:
1472 if dt_stop in self._columns:
1473 arch += ' date_stop="' + dt_stop + '"'
1477 if not dt_stop_flag:
1478 for dt_delay in ["date_delay","planned_hours","x_date_delay","x_planned_hours"]:
1479 if dt_delay in self._columns:
1480 arch += ' date_delay="' + dt_delay + '"'
1484 ' <field name="%s"/>\n'
1485 '</calendar>') % (self._rec_name)
1489 def __get_default_search_view(self, cr, uid, context={}):
1492 if isinstance(s, unicode):
1493 return s.encode('utf8')
1496 view = self.fields_view_get(cr, uid, False, 'form', context)
1498 root = etree.fromstring(encode(view['arch']))
1499 res = etree.XML("<search string='%s'></search>" % root.get("string", ""))
1500 node = etree.Element("group")
1503 fields = root.xpath("//field[@select=1]")
1504 for field in fields:
1507 return etree.tostring(res, encoding="utf-8").replace('\t', '')
1510 # if view_id, view_type is not required
1512 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1514 Get the detailed composition of the requested view like fields, model, view architecture
1516 :param cr: database cursor
1517 :param user: current user id
1518 :param view_id: id of the view or None
1519 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1520 :param context: context arguments, like lang, time zone
1521 :param toolbar: true to include contextual actions
1522 :param submenu: example (portal_project module)
1523 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1524 :raise AttributeError:
1525 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1526 * if some tag other than 'position' is found in parent view
1527 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1534 if isinstance(s, unicode):
1535 return s.encode('utf8')
1538 def _inherit_apply(src, inherit):
1539 def _find(node, node2):
1540 if node2.tag == 'xpath':
1541 res = node.xpath(node2.get('expr'))
1547 for n in node.getiterator(node2.tag):
1549 for attr in node2.attrib:
1550 if attr == 'position':
1553 if n.get(attr) == node2.get(attr):
1560 # End: _find(node, node2)
1562 doc_dest = etree.fromstring(encode(inherit))
1563 toparse = [ doc_dest ]
1566 node2 = toparse.pop(0)
1567 if node2.tag == 'data':
1568 toparse += [ c for c in doc_dest ]
1570 node = _find(src, node2)
1571 if node is not None:
1573 if node2.get('position'):
1574 pos = node2.get('position')
1575 if pos == 'replace':
1576 parent = node.getparent()
1578 src = copy.deepcopy(node2[0])
1581 node.addprevious(child)
1582 node.getparent().remove(node)
1583 elif pos == 'attributes':
1584 for child in node2.getiterator('attribute'):
1585 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1587 node.set(attribute[0], attribute[1])
1589 del(node.attrib[attribute[0]])
1591 sib = node.getnext()
1595 elif pos == 'after':
1599 sib.addprevious(child)
1600 elif pos == 'before':
1601 node.addprevious(child)
1603 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1606 ' %s="%s"' % (attr, node2.get(attr))
1607 for attr in node2.attrib
1608 if attr != 'position'
1610 tag = "<%s%s>" % (node2.tag, attrs)
1611 raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1613 # End: _inherit_apply(src, inherit)
1615 result = {'type': view_type, 'model': self._name}
1621 view_ref = context.get(view_type + '_view_ref', False)
1622 if view_ref and not view_id:
1624 module, view_ref = view_ref.split('.', 1)
1625 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1626 view_ref_res = cr.fetchone()
1628 view_id = view_ref_res[0]
1631 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1634 query += " AND model=%s"
1635 params += (self._name,)
1636 cr.execute(query, params)
1638 cr.execute('''SELECT
1639 arch,name,field_parent,id,type,inherit_id
1646 ORDER BY priority''', (self._name, view_type))
1647 sql_res = cr.fetchone()
1653 view_id = ok or sql_res[3]
1656 # if a view was found
1658 result['type'] = sql_res[4]
1659 result['view_id'] = sql_res[3]
1660 result['arch'] = sql_res[0]
1662 def _inherit_apply_rec(result, inherit_id):
1663 # get all views which inherit from (ie modify) this view
1664 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1665 sql_inherit = cr.fetchall()
1666 for (inherit, id) in sql_inherit:
1667 result = _inherit_apply(result, inherit)
1668 result = _inherit_apply_rec(result, id)
1671 inherit_result = etree.fromstring(encode(result['arch']))
1672 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1674 result['name'] = sql_res[1]
1675 result['field_parent'] = sql_res[2] or False
1678 # otherwise, build some kind of default view
1679 if view_type == 'form':
1680 res = self.fields_get(cr, user, context=context)
1681 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1682 '<form string="%s">' % (self._description,)
1684 if res[x]['type'] not in ('one2many', 'many2many'):
1685 xml += '<field name="%s"/>' % (x,)
1686 if res[x]['type'] == 'text':
1690 elif view_type == 'tree':
1691 _rec_name = self._rec_name
1692 if _rec_name not in self._columns:
1693 _rec_name = self._columns.keys()[0]
1694 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1695 '<tree string="%s"><field name="%s"/></tree>' \
1696 % (self._description, self._rec_name)
1698 elif view_type == 'calendar':
1699 xml = self.__get_default_calendar_view()
1701 elif view_type == 'search':
1702 xml = self.__get_default_search_view(cr, user, context)
1705 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1706 raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type)
1707 result['arch'] = etree.fromstring(encode(xml))
1708 result['name'] = 'default'
1709 result['field_parent'] = False
1710 result['view_id'] = 0
1712 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1713 result['arch'] = xarch
1714 result['fields'] = xfields
1717 if context and context.get('active_id',False):
1718 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1720 act_id = data_menu.id
1722 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1723 result['submenu'] = getattr(data_action,'menus', False)
1727 for key in ('report_sxw_content', 'report_rml_content',
1728 'report_sxw', 'report_rml',
1729 'report_sxw_content_data', 'report_rml_content_data'):
1733 ir_values_obj = self.pool.get('ir.values')
1734 resprint = ir_values_obj.get(cr, user, 'action',
1735 'client_print_multi', [(self._name, False)], False,
1737 resaction = ir_values_obj.get(cr, user, 'action',
1738 'client_action_multi', [(self._name, False)], False,
1741 resrelate = ir_values_obj.get(cr, user, 'action',
1742 'client_action_relate', [(self._name, False)], False,
1744 resprint = map(clean, resprint)
1745 resaction = map(clean, resaction)
1746 resaction = filter(lambda x: not x.get('multi', False), resaction)
1747 resprint = filter(lambda x: not x.get('multi', False), resprint)
1748 resrelate = map(lambda x: x[2], resrelate)
1750 for x in resprint+resaction+resrelate:
1751 x['string'] = x['name']
1753 result['toolbar'] = {
1755 'action': resaction,
1758 if result['type']=='form' and result['arch'].count("default_focus")>1:
1759 msg = "Form View contain more than one default_focus attribute"
1760 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1761 raise except_orm('View Error !',msg)
1764 _view_look_dom_arch = __view_look_dom_arch
1766 def search_count(self, cr, user, args, context=None):
1769 res = self.search(cr, user, args, context=context, count=True)
1770 if isinstance(res, list):
1774 def search(self, cr, user, args, offset=0, limit=None, order=None,
1775 context=None, count=False):
1776 raise NotImplementedError(_('The search method is not implemented on this object !'))
1778 def name_get(self, cr, user, ids, context=None):
1779 raise NotImplementedError(_('The name_get method is not implemented on this object !'))
1781 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1782 raise NotImplementedError(_('The name_search method is not implemented on this object !'))
1784 def copy(self, cr, uid, id, default=None, context=None):
1785 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1787 def exists(self, cr, uid, id, context=None):
1788 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1790 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1793 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1795 fields = self._columns.keys() + self._inherit_fields.keys()
1796 #FIXME: collect all calls to _get_source into one SQL call.
1798 res[lang] = {'code': lang}
1800 if f in self._columns:
1801 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1803 res[lang][f] = res_trans
1805 res[lang][f] = self._columns[f].string
1806 for table in self._inherits:
1807 cols = intersect(self._inherit_fields.keys(), fields)
1808 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1811 res[lang]['code'] = lang
1812 for f in res2[lang]:
1813 res[lang][f] = res2[lang][f]
1816 def write_string(self, cr, uid, id, langs, vals, context=None):
1817 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1818 #FIXME: try to only call the translation in one SQL
1821 if field in self._columns:
1822 src = self._columns[field].string
1823 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1824 for table in self._inherits:
1825 cols = intersect(self._inherit_fields.keys(), vals)
1827 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1830 def _check_removed_columns(self, cr, log=False):
1831 raise NotImplementedError()
1833 class orm_memory(orm_template):
1834 _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']
1835 _inherit_fields = {}
1840 def __init__(self, cr):
1841 super(orm_memory, self).__init__(cr)
1845 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1847 def _check_access(self, uid, object_id, mode):
1848 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1849 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1851 def vaccum(self, cr, uid):
1853 if self.check_id % self._check_time:
1856 max = time.time() - self._max_hours * 60 * 60
1857 for id in self.datas:
1858 if self.datas[id]['internal.date_access'] < max:
1860 self.unlink(cr, 1, tounlink)
1861 if len(self.datas)>self._max_count:
1862 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1864 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1865 self.unlink(cr, uid, ids)
1868 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1871 if not fields_to_read:
1872 fields_to_read = self._columns.keys()
1876 if isinstance(ids, (int, long)):
1880 for f in fields_to_read:
1881 record = self.datas.get(id)
1883 self._check_access(user, id, 'read')
1884 r[f] = record.get(f, False)
1885 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1888 if id in self.datas:
1889 self.datas[id]['internal.date_access'] = time.time()
1890 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1891 for f in fields_post:
1892 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1893 for record in result:
1894 record[f] = res2[record['id']]
1895 if isinstance(ids_orig, (int, long)):
1899 def write(self, cr, user, ids, vals, context=None):
1905 if self._columns[field]._classic_write:
1906 vals2[field] = vals[field]
1908 upd_todo.append(field)
1909 for object_id in ids:
1910 self._check_access(user, object_id, mode='write')
1911 self.datas[object_id].update(vals2)
1912 self.datas[object_id]['internal.date_access'] = time.time()
1913 for field in upd_todo:
1914 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
1915 self._validate(cr, user, [object_id], context)
1916 wf_service = netsvc.LocalService("workflow")
1917 wf_service.trg_write(user, self._name, object_id, cr)
1920 def create(self, cr, user, vals, context=None):
1921 self.vaccum(cr, user)
1923 id_new = self.next_id
1925 # override defaults with the provided values, never allow the other way around
1926 defaults = self.default_get(cr, user, [], context)
1927 defaults.update(vals)
1933 if self._columns[field]._classic_write:
1934 vals2[field] = vals[field]
1936 upd_todo.append(field)
1937 self.datas[id_new] = vals2
1938 self.datas[id_new]['internal.date_access'] = time.time()
1939 self.datas[id_new]['internal.create_uid'] = user
1941 for field in upd_todo:
1942 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1943 self._validate(cr, user, [id_new], context)
1944 if self._log_create and not (context and context.get('no_store_function', False)):
1945 message = self._description + \
1947 self.name_get(cr, user, [id_new], context=context)[0][1] + \
1949 self.log(cr, user, id_new, message, True, context=context)
1950 wf_service = netsvc.LocalService("workflow")
1951 wf_service.trg_create(user, self._name, id_new, cr)
1954 def _where_calc(self, cr, user, args, active_test=True, context=None):
1959 # if the object has a field named 'active', filter out all inactive
1960 # records unless they were explicitely asked for
1961 if 'active' in self._columns and (active_test and context.get('active_test', True)):
1963 active_in_args = False
1965 if a[0] == 'active':
1966 active_in_args = True
1967 if not active_in_args:
1968 args.insert(0, ('active', '=', 1))
1970 args = [('active', '=', 1)]
1973 e = expression.expression(args)
1974 e.parse(cr, user, self, context)
1978 def search(self, cr, user, args, offset=0, limit=None, order=None,
1979 context=None, count=False):
1983 # implicit filter on current user except for superuser
1987 args.insert(0, ('internal.create_uid', '=', user))
1989 result = self._where_calc(cr, user, args, context=context)
1991 return self.datas.keys()
1995 #Find the value of dict
1998 for id, data in self.datas.items():
2001 if limit and (counter > int(limit)):
2006 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2007 elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
2008 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2009 elif arg[1] in ['ilike']:
2010 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2020 def unlink(self, cr, uid, ids, context=None):
2022 self._check_access(uid, id, 'unlink')
2023 self.datas.pop(id, None)
2025 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2028 def perm_read(self, cr, user, ids, context=None, details=True):
2030 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2031 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2033 self._check_access(user, id, 'read')
2035 'create_uid': credentials,
2036 'create_date': create_date,
2038 'write_date': False,
2043 def _check_removed_columns(self, cr, log=False):
2044 # nothing to check in memory...
2047 def exists(self, cr, uid, id, context=None):
2048 return id in self.datas
2050 class orm(orm_template):
2051 _sql_constraints = []
2053 _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']
2055 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2057 Get the list of records in list view grouped by the given ``groupby`` fields
2059 :param cr: database cursor
2060 :param uid: current user id
2061 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2062 :param fields: list of fields present in the list view specified on the object
2063 :param groupby: list of fields on which to groupby the records
2064 :type fields_list: list (example ['field_name_1', ...])
2065 :param offset: optional number of records to skip
2066 :param limit: optional max number of records to return
2067 :param context: context arguments, like lang, time zone
2068 :return: list of dictionaries(one dictionary for each record) containing:
2070 * the values of fields grouped by the fields in ``groupby`` argument
2071 * __domain: list of tuples specifying the search criteria
2072 * __context: dictionary with argument like ``groupby``
2073 :rtype: [{'field_name_1': value, ...]
2074 :raise AccessError: * if user has no read rights on the requested object
2075 * if user tries to bypass access rules for read on the requested object
2078 context = context or {}
2079 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2081 fields = self._columns.keys()
2083 # compute the where, order by, limit and offset clauses
2084 (where_clause, where_clause_params, tables) = self._where_calc(cr, uid, domain, context=context)
2086 # apply direct ir.rules from current model
2087 self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', context=context)
2089 # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
2090 for inherited_model in self._inherits:
2091 previous_tables = list(tables)
2092 if self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
2093 # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
2094 # list of table in case the inherited table was not in the list before (as that means the corresponding
2095 # JOIN(s) was(were) not present)
2096 self._inherits_join_add(inherited_model, previous_tables, where_clause)
2097 tables = list(set(tables).union(set(previous_tables)))
2099 # Take care of adding join(s) if groupby is an '_inherits'ed field
2100 groupby_list = groupby
2102 if groupby and isinstance(groupby, list):
2103 groupby = groupby[0]
2104 tables, where_clause, qfield = self._inherits_join_calc(groupby,tables,where_clause)
2106 if len(where_clause):
2107 where_clause = ' where '+string.join(where_clause, ' and ')
2110 limit_str = limit and ' limit %d' % limit or ''
2111 offset_str = offset and ' offset %d' % offset or ''
2113 fget = self.fields_get(cr, uid, fields)
2114 float_int_fields = filter(lambda x: fget[x]['type'] in ('float','integer'), fields)
2120 if fget.get(groupby,False) and fget[groupby]['type'] in ('date','datetime'):
2121 flist = "to_char(%s,'yyyy-mm') as %s "%(groupby,groupby)
2122 groupby = "to_char(%s,'yyyy-mm')"%(groupby)
2127 fields_pre = [f for f in float_int_fields if
2128 f == self.CONCURRENCY_CHECK_FIELD
2129 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2130 for f in fields_pre:
2131 if f not in ['id','sequence']:
2132 operator = fget[f].get('group_operator','sum')
2135 flist += operator+'('+f+') as '+f
2138 gb = ' group by '+groupby
2141 cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_clause_params)
2144 for r in cr.dictfetchall():
2145 for fld,val in r.items():
2146 if val == None:r[fld] = False
2147 alldata[r['id']] = r
2149 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2152 d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
2153 if not isinstance(groupby_list,(str, unicode)):
2154 if groupby or not context.get('group_by_no_leaf', False):
2155 d['__context'] = {'group_by':groupby_list[1:]}
2156 if groupby and fget.has_key(groupby):
2157 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
2158 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
2159 days = calendar.monthrange(dt.year, dt.month)[1]
2161 d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
2162 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),\
2163 (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
2164 del alldata[d['id']][groupby]
2165 d.update(alldata[d['id']])
2169 def _inherits_join_add(self, parent_model_name, tables, where_clause):
2171 Add missing table SELECT and JOIN clause for reaching the parent table (no duplicates)
2173 :param parent_model_name: name of the parent model for which the clauses should be added
2174 :param tables: list of table._table names enclosed in double quotes as returned
2176 :param where_clause: current list of WHERE clause params
2178 inherits_field = self._inherits[parent_model_name]
2179 parent_model = self.pool.get(parent_model_name)
2180 parent_table_name = parent_model._table
2181 quoted_parent_table_name = '"%s"' % parent_table_name
2182 if quoted_parent_table_name not in tables:
2183 tables.append(quoted_parent_table_name)
2184 where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2185 return (tables, where_clause)
2187 def _inherits_join_calc(self, field, tables, where_clause):
2189 Adds missing table select and join clause(s) for reaching
2190 the field coming from an '_inherits' parent table (no duplicates).
2192 :param tables: list of table._table names enclosed in double quotes as returned
2194 :param where_clause: current list of WHERE clause params
2195 :return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated
2196 versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
2197 in the form ``table.field``, to be referenced in queries.
2199 current_table = self
2200 while field in current_table._inherit_fields and not field in current_table._columns:
2201 parent_model_name = current_table._inherit_fields[field][0]
2202 parent_table = self.pool.get(parent_model_name)
2203 self._inherits_join_add(parent_model_name, tables, where_clause)
2204 current_table = parent_table
2205 return (tables, where_clause, '"%s".%s' % (current_table._table, field))
2207 def _parent_store_compute(self, cr):
2208 if not self._parent_store:
2210 logger = netsvc.Logger()
2211 logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2212 def browse_rec(root, pos=0):
2214 where = self._parent_name+'='+str(root)
2216 where = self._parent_name+' IS NULL'
2217 if self._parent_order:
2218 where += ' order by '+self._parent_order
2219 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2221 childs = cr.fetchall()
2223 pos2 = browse_rec(id[0], pos2)
2224 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
2226 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2227 if self._parent_order:
2228 query += ' order by '+self._parent_order
2231 for (root,) in cr.fetchall():
2232 pos = browse_rec(root, pos)
2235 def _update_store(self, cr, f, k):
2236 logger = netsvc.Logger()
2237 logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2238 ss = self._columns[k]._symbol_set
2239 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2240 cr.execute('select id from '+self._table)
2241 ids_lst = map(lambda x: x[0], cr.fetchall())
2244 ids_lst = ids_lst[40:]
2245 res = f.get(cr, self, iids, k, 1, {})
2246 for key,val in res.items():
2249 # if val is a many2one, just write the ID
2250 if type(val)==tuple:
2252 if (val<>False) or (type(val)<>bool):
2253 cr.execute(update_query, (ss[1](val), key))
2255 def _check_removed_columns(self, cr, log=False):
2256 logger = netsvc.Logger()
2257 # iterate on the database columns to drop the NOT NULL constraints
2258 # of fields which were required but have been removed (or will be added by another module)
2259 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2260 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2261 cr.execute("SELECT a.attname, a.attnotnull"
2262 " FROM pg_class c, pg_attribute a"
2263 " WHERE c.relname=%s"
2264 " AND c.oid=a.attrelid"
2265 " AND a.attisdropped=%s"
2266 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2267 " AND a.attname NOT IN %s" ,(self._table, False, tuple(columns))),
2269 for column in cr.dictfetchall():
2271 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))
2272 if column['attnotnull']:
2273 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2275 def _auto_init(self, cr, context={}):
2276 store_compute = False
2277 logger = netsvc.Logger()
2280 self._field_create(cr, context=context)
2281 if getattr(self, '_auto', True):
2282 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s" ,( self._table,))
2284 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2285 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2288 if self._parent_store:
2289 cr.execute("""SELECT c.relname
2290 FROM pg_class c, pg_attribute a
2291 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2292 """, (self._table, 'parent_left'))
2294 if 'parent_left' not in self._columns:
2295 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2296 if 'parent_right' not in self._columns:
2297 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2298 if self._columns[self._parent_name].ondelete != 'cascade':
2299 logger.notifyChannel('orm', netsvc.LOG_ERROR, "The column %s on object %s must be set as ondelete='cascade'" % (self._parent_name, self._name))
2300 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2301 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2303 store_compute = True
2305 if self._log_access:
2307 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2308 'create_date': 'TIMESTAMP',
2309 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2310 'write_date': 'TIMESTAMP'
2315 FROM pg_class c, pg_attribute a
2316 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2317 """, (self._table, k))
2319 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2322 self._check_removed_columns(cr, log=False)
2324 # iterate on the "object columns"
2325 todo_update_store = []
2326 update_custom_fields = context.get('update_custom_fields', False)
2327 for k in self._columns:
2328 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2330 #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2331 #Not Updating Custom fields
2332 if k.startswith('x_') and not update_custom_fields:
2334 f = self._columns[k]
2336 if isinstance(f, fields.one2many):
2337 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2339 if self.pool.get(f._obj):
2340 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2341 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2342 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2345 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))
2346 res = cr.fetchone()[0]
2348 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2349 elif isinstance(f, fields.many2many):
2350 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2351 if not cr.dictfetchall():
2352 if not self.pool.get(f._obj):
2353 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2354 ref = self.pool.get(f._obj)._table
2355 # ref = f._obj.replace('.', '_')
2356 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))
2357 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2358 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2359 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2362 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 " \
2363 "FROM pg_class c,pg_attribute a,pg_type t " \
2364 "WHERE c.relname=%s " \
2365 "AND a.attname=%s " \
2366 "AND c.oid=a.attrelid " \
2367 "AND a.atttypid=t.oid", (self._table, k))
2368 res = cr.dictfetchall()
2369 if not res and hasattr(f,'oldname'):
2370 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 " \
2371 "FROM pg_class c,pg_attribute a,pg_type t " \
2372 "WHERE c.relname=%s " \
2373 "AND a.attname=%s " \
2374 "AND c.oid=a.attrelid " \
2375 "AND a.atttypid=t.oid", (self._table, f.oldname))
2376 res_old = cr.dictfetchall()
2377 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2378 if res_old and len(res_old)==1:
2379 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2381 res[0]['attname'] = k
2386 f_pg_type = f_pg_def['typname']
2387 f_pg_size = f_pg_def['size']
2388 f_pg_notnull = f_pg_def['attnotnull']
2389 if isinstance(f, fields.function) and not f.store and\
2390 not getattr(f, 'nodrop', False):
2391 logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2392 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2396 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2401 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2402 ('varchar', 'text', 'TEXT', ''),
2403 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2404 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2405 ('timestamp', 'date', 'date', '::date'),
2406 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2407 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2409 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2410 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2411 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2412 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2413 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2414 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2417 if (f_pg_type==c[0]) and (f._type==c[1]):
2418 if f_pg_type != f_obj_type:
2419 if f_pg_type != f_obj_type:
2420 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2422 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2423 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2424 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2425 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2429 if f_pg_type != f_obj_type:
2433 newname = self._table + '_moved' + str(i)
2434 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2435 "WHERE c.relname=%s " \
2436 "AND a.attname=%s " \
2437 "AND c.oid=a.attrelid ", (self._table, newname))
2438 if not cr.fetchone()[0]:
2441 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))
2443 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2444 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2445 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2446 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2448 # if the field is required and hasn't got a NOT NULL constraint
2449 if f.required and f_pg_notnull == 0:
2450 # set the field to the default value if any
2451 if k in self._defaults:
2452 if callable(self._defaults[k]):
2453 default = self._defaults[k](self, cr, 1, context)
2455 default = self._defaults[k]
2457 if (default is not None):
2458 ss = self._columns[k]._symbol_set
2459 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2460 cr.execute(query, (ss[1](default),))
2461 # add the NOT NULL constraint
2464 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2467 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))
2469 elif not f.required and f_pg_notnull == 1:
2470 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2474 indexname = '%s_%s_index' % (self._table, k)
2475 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2476 res2 = cr.dictfetchall()
2477 if not res2 and f.select:
2478 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2480 if f._type == 'text':
2481 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2482 logger.notifyChannel('orm', netsvc.LOG_WARNING, "Adding (b-tree) index for text column '%s' in table '%s'."\
2483 "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"\
2484 "Use a search view instead if you simply want to make the field searchable." % (k, f._type, self._table))
2485 if res2 and not f.select:
2486 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2488 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))
2490 if isinstance(f, fields.many2one):
2491 ref = self.pool.get(f._obj)._table
2492 if ref != 'ir_actions':
2493 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2494 'pg_attribute as att1, pg_attribute as att2 '
2495 'WHERE con.conrelid = cl1.oid '
2496 'AND cl1.relname = %s '
2497 'AND con.confrelid = cl2.oid '
2498 'AND cl2.relname = %s '
2499 'AND array_lower(con.conkey, 1) = 1 '
2500 'AND con.conkey[1] = att1.attnum '
2501 'AND att1.attrelid = cl1.oid '
2502 'AND att1.attname = %s '
2503 'AND array_lower(con.confkey, 1) = 1 '
2504 'AND con.confkey[1] = att2.attnum '
2505 'AND att2.attrelid = cl2.oid '
2506 'AND att2.attname = %s '
2507 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2508 res2 = cr.dictfetchall()
2510 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2511 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2512 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2515 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2517 if not isinstance(f, fields.function) or f.store:
2519 # add the missing field
2520 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2521 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2524 if not create and k in self._defaults:
2525 if callable(self._defaults[k]):
2526 default = self._defaults[k](self, cr, 1, context)
2528 default = self._defaults[k]
2530 ss = self._columns[k]._symbol_set
2531 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2532 cr.execute(query, (ss[1](default),))
2534 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2536 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2538 if isinstance(f, fields.function):
2540 if f.store is not True:
2541 order = f.store[f.store.keys()[0]][2]
2542 todo_update_store.append((order, f,k))
2544 # and add constraints if needed
2545 if isinstance(f, fields.many2one):
2546 if not self.pool.get(f._obj):
2547 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2548 ref = self.pool.get(f._obj)._table
2549 # ref = f._obj.replace('.', '_')
2550 # ir_actions is inherited so foreign key doesn't work on it
2551 if ref != 'ir_actions':
2552 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2554 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2558 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2560 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))
2562 for order,f,k in todo_update_store:
2563 todo_end.append((order, self._update_store, (f, k)))
2566 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2567 create = not bool(cr.fetchone())
2569 cr.commit() # start a new transaction
2571 for (key, con, _) in self._sql_constraints:
2572 conname = '%s_%s' % (self._table, key)
2573 cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2574 if not cr.dictfetchall():
2575 query = 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,)
2580 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))
2584 if hasattr(self, "_sql"):
2585 for line in self._sql.split(';'):
2586 line2 = line.replace('\n', '').strip()
2591 self._parent_store_compute(cr)
2595 def __init__(self, cr):
2596 super(orm, self).__init__(cr)
2598 if not hasattr(self, '_log_access'):
2599 # if not access is not specify, it is the same value as _auto
2600 self._log_access = getattr(self, "_auto", True)
2602 self._columns = self._columns.copy()
2603 for store_field in self._columns:
2604 f = self._columns[store_field]
2605 if hasattr(f, 'digits_change'):
2607 if not isinstance(f, fields.function):
2611 if self._columns[store_field].store is True:
2612 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2614 sm = self._columns[store_field].store
2615 for object, aa in sm.items():
2617 (fnct,fields2,order,length)=aa
2619 (fnct,fields2,order)=aa
2622 raise except_orm('Error',
2623 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2624 self.pool._store_function.setdefault(object, [])
2626 for x,y,z,e,f,l in self.pool._store_function[object]:
2627 if (x==self._name) and (y==store_field) and (e==fields2):
2631 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2632 self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2634 for (key, _, msg) in self._sql_constraints:
2635 self.pool._sql_error[self._table+'_'+key] = msg
2637 # Load manual fields
2639 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2641 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2642 for field in cr.dictfetchall():
2643 if field['name'] in self._columns:
2646 'string': field['field_description'],
2647 'required': bool(field['required']),
2648 'readonly': bool(field['readonly']),
2649 'domain': field['domain'] or None,
2650 'size': field['size'],
2651 'ondelete': field['on_delete'],
2652 'translate': (field['translate']),
2653 #'select': int(field['select_level'])
2656 if field['ttype'] == 'selection':
2657 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2658 elif field['ttype'] == 'reference':
2659 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2660 elif field['ttype'] == 'many2one':
2661 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2662 elif field['ttype'] == 'one2many':
2663 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2664 elif field['ttype'] == 'many2many':
2665 _rel1 = field['relation'].replace('.', '_')
2666 _rel2 = field['model'].replace('.', '_')
2667 _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2668 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2670 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2671 self._inherits_check()
2672 self._inherits_reload()
2673 if not self._sequence:
2674 self._sequence = self._table+'_id_seq'
2675 for k in self._defaults:
2676 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,)
2677 for f in self._columns:
2678 self._columns[f].restart()
2681 # Update objects that uses this one to update their _inherits fields
2684 def _inherits_reload_src(self):
2685 for obj in self.pool.obj_pool.values():
2686 if self._name in obj._inherits:
2687 obj._inherits_reload()
2689 def _inherits_reload(self):
2691 for table in self._inherits:
2692 res.update(self.pool.get(table)._inherit_fields)
2693 for col in self.pool.get(table)._columns.keys():
2694 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2695 for col in self.pool.get(table)._inherit_fields.keys():
2696 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2697 self._inherit_fields = res
2698 self._inherits_reload_src()
2700 def _inherits_check(self):
2701 for table, field_name in self._inherits.items():
2702 if field_name not in self._columns:
2703 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2704 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2705 required=True, ondelete="cascade")
2706 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2707 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))
2708 self._columns[field_name].required = True
2709 self._columns[field_name].ondelete = "cascade"
2711 #def __getattr__(self, name):
2713 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2714 # (though inherits doesn't use Python inheritance).
2715 # Handles translating between local ids and remote ids.
2716 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2717 # when you have inherits.
2719 # for model, field in self._inherits.iteritems():
2720 # proxy = self.pool.get(model)
2721 # if hasattr(proxy, name):
2722 # attribute = getattr(proxy, name)
2723 # if not hasattr(attribute, '__call__'):
2727 # return super(orm, self).__getattr__(name)
2729 # def _proxy(cr, uid, ids, *args, **kwargs):
2730 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2731 # lst = [obj[field].id for obj in objects if obj[field]]
2732 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2737 def fields_get(self, cr, user, fields=None, context=None):
2739 Get the description of list of fields
2741 :param cr: database cursor
2742 :param user: current user id
2743 :param fields: list of fields
2744 :param context: context arguments, like lang, time zone
2745 :return: dictionary of field dictionaries, each one describing a field of the business object
2746 :raise AccessError: * if user has no create/write rights on the requested object
2749 ira = self.pool.get('ir.model.access')
2750 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2751 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2752 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2754 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2756 Read records with given ids with the given fields
2758 :param cr: database cursor
2759 :param user: current user id
2760 :param ids: id or list of the ids of the records to read
2761 :param fields: optional list of field names to return (default: all fields would be returned)
2762 :type fields: list (example ['field_name_1', ...])
2763 :param context: (optional) context arguments, like lang, time zone
2764 :return: list of dictionaries((dictionary per record asked)) with requested field values
2765 :rtype: [{‘name_of_the_field’: value, ...}, ...]
2766 :raise AccessError: * if user has no read rights on the requested object
2767 * if user tries to bypass access rules for read on the requested object
2772 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2774 fields = self._columns.keys() + self._inherit_fields.keys()
2775 if isinstance(ids, (int, long)):
2779 select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2780 result = self._read_flat(cr, user, select, fields, context, load)
2783 for key, v in r.items():
2786 if key in self._columns:
2787 column = self._columns[key]
2788 elif key in self._inherit_fields:
2789 column = self._inherit_fields[key][2]
2792 if v and column._type == 'reference':
2793 model_name, ref_id = v.split(',', 1)
2794 model = self.pool.get(model_name)
2798 cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
2799 reset = not cr.fetchone()[0]
2801 if column._classic_write:
2802 query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
2803 cr.execute(query, (r['id'],))
2806 if isinstance(ids, (int, long, dict)):
2807 return result and result[0] or False
2810 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2815 if fields_to_read == None:
2816 fields_to_read = self._columns.keys()
2818 # Construct a clause for the security rules.
2819 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2820 # or will at least contain self._table.
2821 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2823 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2824 fields_pre = [f for f in fields_to_read if
2825 f == self.CONCURRENCY_CHECK_FIELD
2826 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2827 ] + self._inherits.values()
2831 def convert_field(f):
2832 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2833 if f in ('create_date', 'write_date'):
2834 return "date_trunc('second', %s) as %s" % (f_qual, f)
2835 if f == self.CONCURRENCY_CHECK_FIELD:
2836 if self._log_access:
2837 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2838 return "now()::timestamp AS %s" % (f,)
2839 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2840 return 'length(%s) as "%s"' % (f_qual, f)
2843 fields_pre2 = map(convert_field, fields_pre)
2844 order_by = self._parent_order or self._order
2845 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2846 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2848 query += " AND " + (' OR '.join(rule_clause))
2849 query += " ORDER BY " + order_by
2850 for sub_ids in cr.split_for_in_conditions(ids):
2852 cr.execute(query, [tuple(sub_ids)] + rule_params)
2853 if cr.rowcount != len(sub_ids):
2854 raise except_orm(_('AccessError'),
2855 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2857 cr.execute(query, (tuple(sub_ids),))
2858 res.extend(cr.dictfetchall())
2860 res = map(lambda x: {'id': x}, ids)
2862 for f in fields_pre:
2863 if f == self.CONCURRENCY_CHECK_FIELD:
2865 if self._columns[f].translate:
2866 ids = [x['id'] for x in res]
2867 #TODO: optimize out of this loop
2868 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2870 r[f] = res_trans.get(r['id'], False) or r[f]
2872 for table in self._inherits:
2873 col = self._inherits[table]
2874 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
2877 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
2885 if not record[col]:# if the record is deleted from _inherits table?
2887 record.update(res3[record[col]])
2888 if col not in fields_to_read:
2891 # all fields which need to be post-processed by a simple function (symbol_get)
2892 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
2895 for f in fields_post:
2896 r[f] = self._columns[f]._symbol_get(r[f])
2897 ids = [x['id'] for x in res]
2899 # all non inherited fields for which the attribute whose name is in load is False
2900 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2902 # Compute POST fields
2904 for f in fields_post:
2905 todo.setdefault(self._columns[f]._multi, [])
2906 todo[self._columns[f]._multi].append(f)
2907 for key,val in todo.items():
2909 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2912 if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
2913 record[pos] = res2[record['id']][pos]
2916 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2919 record[f] = res2[record['id']]
2924 for field in vals.copy():
2926 if field in self._columns:
2927 fobj = self._columns[field]
2934 for group in groups:
2935 module = group.split(".")[0]
2936 grp = group.split(".")[1]
2937 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" \
2938 (grp, module, 'res.groups', user))
2939 readonly = cr.fetchall()
2940 if readonly[0][0] >= 1:
2943 elif readonly[0][0] == 0:
2949 if type(vals[field]) == type([]):
2951 elif type(vals[field]) == type(0.0):
2953 elif type(vals[field]) == type(''):
2954 vals[field] = '=No Permission='
2959 def perm_read(self, cr, user, ids, context=None, details=True):
2961 Read the permission for record of the given ids
2963 :param cr: database cursor
2964 :param user: current user id
2965 :param ids: id or list of ids
2966 :param context: context arguments, like lang, time zone
2967 :param details: if True, \*_uid fields are replaced with the name of the user
2968 :return: list of ownership dictionaries for each requested record
2969 :rtype: list of dictionaries with the following keys:
2972 * create_uid: user who created the record
2973 * create_date: date when the record was created
2974 * write_uid: last user who changed the record
2975 * write_date: date of the last change to the record
2983 uniq = isinstance(ids, (int, long))
2987 if self._log_access:
2988 fields += ', create_uid, create_date, write_uid, write_date'
2989 query = 'SELECT %s FROM "%s" WHERE id IN %%s' % (fields, self._table)
2990 cr.execute(query, (tuple(ids),))
2991 res = cr.dictfetchall()
2994 r[key] = r[key] or False
2995 if key in ('write_uid', 'create_uid', 'uid') and details:
2997 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3002 def _check_concurrency(self, cr, ids, context):
3005 if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
3007 return "%s,%s" % (self._name, oid)
3008 santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3009 for i in range(0, len(ids), cr.IN_MAX):
3010 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
3011 for oid in ids[i:i+cr.IN_MAX]
3012 if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
3014 cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
3017 raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
3019 def check_access_rule(self, cr, uid, ids, operation, context=None):
3020 """Verifies that the operation given by ``operation`` is allowed for the user
3021 according to ir.rules.
3023 :param operation: one of ``write``, ``unlink``
3024 :raise except_orm: * if current ir.rules do not permit this operation.
3025 :return: None if the operation is allowed
3027 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3029 where_clause = ' and ' + ' and '.join(where_clause)
3030 for sub_ids in cr.split_for_in_conditions(ids):
3031 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3032 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3033 [sub_ids] + where_params)
3034 if cr.rowcount != len(sub_ids):
3035 raise except_orm(_('AccessError'),
3036 _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
3037 % (operation, self._name))
3039 def unlink(self, cr, uid, ids, context=None):
3041 Delete records with given ids
3043 :param cr: database cursor
3044 :param uid: current user id
3045 :param ids: id or list of ids
3046 :param context: (optional) context arguments, like lang, time zone
3048 :raise AccessError: * if user has no unlink rights on the requested object
3049 * if user tries to bypass access rules for unlink on the requested object
3050 :raise UserError: if the record is default property for other records
3055 if isinstance(ids, (int, long)):
3058 result_store = self._store_get_values(cr, uid, ids, None, context)
3060 self._check_concurrency(cr, ids, context)
3062 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3064 properties = self.pool.get('ir.property')
3065 domain = [('res_id', '=', False),
3066 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3068 if properties.search(cr, uid, domain, context=context):
3069 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3071 wf_service = netsvc.LocalService("workflow")
3073 wf_service.trg_delete(uid, self._name, oid, cr)
3076 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3077 for sub_ids in cr.split_for_in_conditions(ids):
3078 cr.execute('delete from ' + self._table + ' ' \
3079 'where id IN %s', (sub_ids,))
3080 for order, object, store_ids, fields in result_store:
3081 if object != self._name:
3082 obj = self.pool.get(object)
3083 cr.execute('select id from '+obj._table+' where id IN %s',(tuple(store_ids),))
3084 rids = map(lambda x: x[0], cr.fetchall())
3086 obj._store_set_values(cr, uid, rids, fields, context)
3092 def write(self, cr, user, ids, vals, context=None):
3094 Update records with given ids with the given field values
3096 :param cr: database cursor
3097 :param user: current user id
3099 :param ids: object id or list of object ids to update according to **vals**
3100 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3101 :type vals: dictionary
3102 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3103 :type context: dictionary
3105 :raise AccessError: * if user has no write rights on the requested object
3106 * if user tries to bypass access rules for write on the requested object
3107 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3108 :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)
3110 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3112 + For a many2many field, a list of tuples is expected.
3113 Here is the list of tuple that are accepted, with the corresponding semantics ::
3115 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3116 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3117 (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)
3118 (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)
3119 (4, ID) link to existing record with id = ID (adds a relationship)
3120 (5) unlink all (like using (3,ID) for all linked records)
3121 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3124 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3126 + For a one2many field, a lits of tuples is expected.
3127 Here is the list of tuple that are accepted, with the corresponding semantics ::
3129 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3130 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3131 (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)
3134 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3136 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3137 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3141 for field in vals.copy():
3143 if field in self._columns:
3144 fobj = self._columns[field]
3146 fobj = self._inherit_fields[field][2]
3153 for group in groups:
3154 module = group.split(".")[0]
3155 grp = group.split(".")[1]
3156 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" \
3157 (grp, module, 'res.groups', user))
3158 readonly = cr.fetchall()
3159 if readonly[0][0] >= 1:
3162 elif readonly[0][0] == 0:
3174 if isinstance(ids, (int, long)):
3177 self._check_concurrency(cr, ids, context)
3178 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3180 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3182 # No direct update of parent_left/right
3183 vals.pop('parent_left', None)
3184 vals.pop('parent_right', None)
3186 parents_changed = []
3187 if self._parent_store and (self._parent_name in vals):
3188 # The parent_left/right computation may take up to
3189 # 5 seconds. No need to recompute the values if the
3190 # parent is the same. Get the current value of the parent
3191 parent_val = vals[self._parent_name]
3193 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3194 (self._table, self._parent_name, self._parent_name)
3195 cr.execute(query, (tuple(ids), parent_val))
3197 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3198 (self._table, self._parent_name)
3199 cr.execute(query, (tuple(ids),))
3200 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3207 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3209 if field in self._columns:
3210 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3211 if (not totranslate) or not self._columns[field].translate:
3212 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3213 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3214 direct.append(field)
3216 upd_todo.append(field)
3218 updend.append(field)
3219 if field in self._columns \
3220 and hasattr(self._columns[field], 'selection') \
3222 if self._columns[field]._type == 'reference':
3223 val = vals[field].split(',')[0]
3226 if isinstance(self._columns[field].selection, (tuple, list)):
3227 if val not in dict(self._columns[field].selection):
3228 raise except_orm(_('ValidateError'),
3229 _('The value "%s" for the field "%s" is not in the selection') \
3230 % (vals[field], field))
3232 if val not in dict(self._columns[field].selection(
3233 self, cr, user, context=context)):
3234 raise except_orm(_('ValidateError'),
3235 _('The value "%s" for the field "%s" is not in the selection') \
3236 % (vals[field], field))
3238 if self._log_access:
3239 upd0.append('write_uid=%s')
3240 upd0.append('write_date=now()')
3244 self.check_access_rule(cr, user, ids, 'write', context=context)
3245 for sub_ids in cr.split_for_in_conditions(ids):
3246 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3247 'where id IN %s', upd1 + [sub_ids])
3252 if self._columns[f].translate:
3253 src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
3256 # Inserting value to DB
3257 self.write(cr, user, ids, {f:vals[f]})
3258 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3261 # call the 'set' method of fields which are not classic_write
3262 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3264 # default element in context must be removed when call a one2many or many2many
3265 rel_context = context.copy()
3266 for c in context.items():
3267 if c[0].startswith('default_'):
3268 del rel_context[c[0]]
3270 for field in upd_todo:
3272 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3274 for table in self._inherits:
3275 col = self._inherits[table]
3277 for sub_ids in cr.split_for_in_conditions(ids):
3278 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3279 'where id IN %s', (sub_ids,))
3280 nids.extend([x[0] for x in cr.fetchall()])
3284 if self._inherit_fields[val][0] == table:
3286 self.pool.get(table).write(cr, user, nids, v, context)
3288 self._validate(cr, user, ids, context)
3290 # TODO: use _order to set dest at the right position and not first node of parent
3291 # We can't defer parent_store computation because the stored function
3292 # fields that are computer may refer (directly or indirectly) to
3293 # parent_left/right (via a child_of domain)
3296 self.pool._init_parent[self._name]=True
3298 order = self._parent_order or self._order
3299 parent_val = vals[self._parent_name]
3301 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3303 clause, params = '%s IS NULL' % (self._parent_name,), ()
3304 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3305 parents = cr.fetchall()
3307 for id in parents_changed:
3308 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3309 pleft, pright = cr.fetchone()
3310 distance = pright - pleft + 1
3312 # Find Position of the element
3314 for (parent_pright, parent_id) in parents:
3317 position = parent_pright+1
3319 # It's the first node of the parent
3324 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3325 position = cr.fetchone()[0]+1
3327 if pleft < position <= pright:
3328 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3330 if pleft < position:
3331 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3332 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3333 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))
3335 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3336 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3337 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))
3339 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3343 for order, object, ids, fields in result:
3344 key = (object,tuple(fields))
3345 done.setdefault(key, {})
3346 # avoid to do several times the same computation
3349 if id not in done[key]:
3350 done[key][id] = True
3352 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3354 wf_service = netsvc.LocalService("workflow")
3356 wf_service.trg_write(user, self._name, id, cr)
3360 # TODO: Should set perm to user.xxx
3362 def create(self, cr, user, vals, context=None):
3364 Create new record with specified value
3366 :param cr: database cursor
3367 :param user: current user id
3369 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3370 :type vals: dictionary
3371 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3372 :type context: dictionary
3373 :return: id of new record created
3374 :raise AccessError: * if user has no create rights on the requested object
3375 * if user tries to bypass access rules for create on the requested object
3376 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3377 :raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
3379 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3380 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3386 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3391 for (t, c) in self._inherits.items():
3393 avoid_table.append(t)
3394 for f in self._columns.keys(): # + self._inherit_fields.keys():
3398 for f in self._inherit_fields.keys():
3399 if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
3403 default_values = self.default_get(cr, user, default, context)
3404 for dv in default_values:
3405 if dv in self._columns and self._columns[dv]._type == 'many2many':
3406 if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
3407 default_values[dv] = [(6, 0, default_values[dv])]
3409 # override defaults with the provided values, never allow the other way around
3410 default_values.update(vals)
3411 vals = default_values
3414 for v in self._inherits:
3415 if self._inherits[v] not in vals:
3418 tocreate[v] = {'id' : vals[self._inherits[v]]}
3419 (upd0, upd1, upd2) = ('', '', [])
3421 for v in vals.keys():
3422 if v in self._inherit_fields:
3423 (table, col, col_detail) = self._inherit_fields[v]
3424 tocreate[table][v] = vals[v]
3427 if (v not in self._inherit_fields) and (v not in self._columns):
3430 # Try-except added to filter the creation of those records whose filds are readonly.
3431 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3433 cr.execute("SELECT nextval('"+self._sequence+"')")
3435 raise except_orm(_('UserError'),
3436 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3438 id_new = cr.fetchone()[0]
3439 for table in tocreate:
3440 if self._inherits[table] in vals:
3441 del vals[self._inherits[table]]
3443 record_id = tocreate[table].pop('id', None)
3445 if record_id is None or not record_id:
3446 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3448 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3450 upd0 += ','+self._inherits[table]
3452 upd2.append(record_id)
3454 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3455 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3457 for bool_field in bool_fields:
3458 if bool_field not in vals:
3459 vals[bool_field] = False
3461 for field in vals.copy():
3463 if field in self._columns:
3464 fobj = self._columns[field]
3466 fobj = self._inherit_fields[field][2]
3472 for group in groups:
3473 module = group.split(".")[0]
3474 grp = group.split(".")[1]
3475 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" % \
3476 (grp, module, 'res.groups', user))
3477 readonly = cr.fetchall()
3478 if readonly[0][0] >= 1:
3481 elif readonly[0][0] == 0:
3489 if self._columns[field]._classic_write:
3490 upd0 = upd0 + ',"' + field + '"'
3491 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3492 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3494 if not isinstance(self._columns[field], fields.related):
3495 upd_todo.append(field)
3496 if field in self._columns \
3497 and hasattr(self._columns[field], 'selection') \
3499 if self._columns[field]._type == 'reference':
3500 val = vals[field].split(',')[0]
3503 if isinstance(self._columns[field].selection, (tuple, list)):
3504 if val not in dict(self._columns[field].selection):
3505 raise except_orm(_('ValidateError'),
3506 _('The value "%s" for the field "%s" is not in the selection') \
3507 % (vals[field], field))
3509 if val not in dict(self._columns[field].selection(
3510 self, cr, user, context=context)):
3511 raise except_orm(_('ValidateError'),
3512 _('The value "%s" for the field "%s" is not in the selection') \
3513 % (vals[field], field))
3514 if self._log_access:
3515 upd0 += ',create_uid,create_date'
3518 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3519 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3520 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3522 if self._parent_store and not context.get('defer_parent_store_computation'):
3524 self.pool._init_parent[self._name]=True
3526 parent = vals.get(self._parent_name, False)
3528 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3530 result_p = cr.fetchall()
3531 for (pleft,) in result_p:
3536 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3537 pleft_old = cr.fetchone()[0]
3540 cr.execute('select max(parent_right) from '+self._table)
3541 pleft = cr.fetchone()[0] or 0
3542 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3543 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3544 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3546 # default element in context must be remove when call a one2many or many2many
3547 rel_context = context.copy()
3548 for c in context.items():
3549 if c[0].startswith('default_'):
3550 del rel_context[c[0]]
3553 for field in upd_todo:
3554 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3555 self._validate(cr, user, [id_new], context)
3557 if not context.get('no_store_function', False):
3558 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3561 for order, object, ids, fields2 in result:
3562 if not (object, ids, fields2) in done:
3563 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3564 done.append((object, ids, fields2))
3566 if self._log_create and not (context and context.get('no_store_function', False)):
3567 message = self._description + \
3569 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3571 self.log(cr, user, id_new, message, True, context=context)
3572 wf_service = netsvc.LocalService("workflow")
3573 wf_service.trg_create(user, self._name, id_new, cr)
3576 def _store_get_values(self, cr, uid, ids, fields, context):
3578 fncts = self.pool._store_function.get(self._name, [])
3579 for fnct in range(len(fncts)):
3584 for f in (fields or []):
3585 if f in fncts[fnct][3]:
3591 result.setdefault(fncts[fnct][0], {})
3593 # uid == 1 for accessing objects having rules defined on store fields
3594 ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3595 for id in filter(None, ids2):
3596 result[fncts[fnct][0]].setdefault(id, [])
3597 result[fncts[fnct][0]][id].append(fnct)
3599 for object in result:
3601 for id,fnct in result[object].items():
3602 k2.setdefault(tuple(fnct), [])
3603 k2[tuple(fnct)].append(id)
3604 for fnct,id in k2.items():
3605 dict.setdefault(fncts[fnct[0]][4],[])
3606 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3614 def _store_set_values(self, cr, uid, ids, fields, context):
3619 if self._log_access:
3620 cr.execute('select id,write_date from '+self._table+' where id IN %s',(tuple(ids),))
3624 field_dict.setdefault(r[0], [])
3625 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3626 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3627 for i in self.pool._store_function.get(self._name, []):
3629 up_write_date = write_date + datetime.timedelta(hours=i[5])
3630 if datetime.datetime.now() < up_write_date:
3632 field_dict[r[0]].append(i[1])
3638 if self._columns[f]._multi not in keys:
3639 keys.append(self._columns[f]._multi)
3640 todo.setdefault(self._columns[f]._multi, [])
3641 todo[self._columns[f]._multi].append(f)
3645 # uid == 1 for accessing objects having rules defined on store fields
3646 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3647 for id,value in result.items():
3649 for f in value.keys():
3650 if f in field_dict[id]:
3657 if self._columns[v]._type in ('many2one', 'one2one'):
3659 value[v] = value[v][0]
3662 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3663 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3666 cr.execute('update "' + self._table + '" set ' + \
3667 string.join(upd0, ',') + ' where id = %s', upd1)
3671 # uid == 1 for accessing objects having rules defined on store fields
3672 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3673 for r in result.keys():
3675 if r in field_dict.keys():
3676 if f in field_dict[r]:
3678 for id,value in result.items():
3679 if self._columns[f]._type in ('many2one', 'one2one'):
3684 cr.execute('update "' + self._table + '" set ' + \
3685 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3691 def perm_write(self, cr, user, ids, fields, context=None):
3692 raise NotImplementedError(_('This method does not exist anymore'))
3694 # TODO: ameliorer avec NULL
3695 def _where_calc(self, cr, user, args, active_test=True, context=None):
3696 """Computes the WHERE clause needed to implement an OpenERP domain.
3697 :param args: the domain to compute
3699 :param active_test: whether the default filtering of records with ``active``
3700 field set to ``False`` should be applied.
3701 :return: tuple with 3 elements: (where_clause, where_clause_params, tables) where
3702 ``where_clause`` contains a list of where clause elements (to be joined with 'AND'),
3703 ``where_clause_params`` is a list of parameters to be passed to the db layer
3704 for the where_clause expansion, and ``tables`` is the list of double-quoted
3705 table names that need to be included in the FROM clause.
3711 # if the object has a field named 'active', filter out all inactive
3712 # records unless they were explicitely asked for
3713 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3715 active_in_args = False
3717 if a[0] == 'active':
3718 active_in_args = True
3719 if not active_in_args:
3720 args.insert(0, ('active', '=', 1))
3722 args = [('active', '=', 1)]
3726 e = expression.expression(args)
3727 e.parse(cr, user, self, context)
3728 tables = e.get_tables()
3729 qu1, qu2 = e.to_sql()
3730 qu1 = qu1 and [qu1] or []
3732 qu1, qu2, tables = [], [], ['"%s"' % self._table]
3734 return (qu1, qu2, tables)
3736 def _check_qorder(self, word):
3737 if not regex_order.match(word):
3738 raise except_orm(_('AccessError'), _('Bad query.'))
3741 def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
3742 """Add what's missing in ``where_clause``, ``where_params``, ``tables`` to implement
3743 all appropriate ir.rules (on the current object but also from it's _inherits parents)
3745 :param where_clause: list with current elements of the WHERE clause (strings)
3746 :param where_clause_params: list with parameters for ``where_clause``
3747 :param tables: list with double-quoted names of the tables that are joined
3749 :param model_name: optional name of the model whose ir.rules should be applied (default:``self._name``)
3750 This could be useful for inheritance for example, but there is no provision to include
3751 the appropriate JOIN for linking the current model to the one referenced in model_name.
3752 :return: True if additional clauses where applied.
3754 added_clause, added_params, added_tables = self.pool.get('ir.rule').domain_get(cr, uid, model_name or self._name, mode, context=context)
3756 where_clause += added_clause
3757 where_clause_params += added_params
3758 for table in added_tables:
3759 if table not in tables:
3760 tables.append(table)
3764 def search(self, cr, user, args, offset=0, limit=None, order=None,
3765 context=None, count=False):
3767 Search for record/s based on a search domain.
3769 :param cr: database cursor
3770 :param user: current user id
3771 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
3772 :param offset: optional number of results to skip in the returned values (default: 0)
3773 :param limit: optional max number of records to return (default: **None**)
3774 :param order: optional columns to sort by (default: self._order=id )
3775 :param context: optional context arguments, like lang, time zone
3776 :type context: dictionary
3777 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
3778 :return: id or list of ids of records matching the criteria
3779 :rtype: integer or list of integers
3780 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
3782 **Expressing a search domain (args)**
3784 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
3786 * **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.
3787 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
3788 The semantics of most of these operators are obvious.
3789 The ``child_of`` operator will look for records who are children or grand-children of a given record,
3790 according to the semantics of this model (i.e following the relationship field named by
3791 ``self._parent_name``, by default ``parent_id``.
3792 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
3794 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
3795 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
3796 Be very careful about this when you combine them the first time.
3798 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
3800 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
3802 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::
3804 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
3809 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3810 # compute the where, order by, limit and offset clauses
3811 (where_clause, where_clause_params, tables) = self._where_calc(cr, user, args, context=context)
3813 # apply direct ir.rules from current model
3814 self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', context=context)
3816 # then apply the ir.rules from the parents (through _inherits), adding the appropriate JOINs if needed
3817 for inherited_model in self._inherits:
3818 previous_tables = list(tables)
3819 if self._apply_ir_rules(cr, user, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
3820 # if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
3821 # list of table in case the inherited table was not in the list before (as that means the corresponding
3822 # JOIN(s) was(were) not present)
3823 self._inherits_join_add(inherited_model, previous_tables, where_clause)
3824 tables = list(set(tables).union(set(previous_tables)))
3826 where = where_clause
3828 order_by = self._order
3830 self._check_qorder(order)
3831 o = order.split(' ')[0]
3832 if (o in self._columns):
3833 # we can only do efficient sort if the fields is stored in database
3834 if getattr(self._columns[o], '_classic_read'):
3836 elif (o in self._inherit_fields):
3837 parent_obj = self.pool.get(self._inherit_fields[o][0])
3838 if getattr(parent_obj._columns[o], '_classic_read'):
3839 # Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
3840 inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
3842 limit_str = limit and ' limit %d' % limit or ''
3843 offset_str = offset and ' offset %d' % offset or ''
3846 where_str = " WHERE %s" % " AND ".join(where)
3851 cr.execute('select count(%s.id) from ' % self._table +
3852 ','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
3855 cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params)
3857 return [x[0] for x in res]
3859 # returns the different values ever entered for one field
3860 # this is used, for example, in the client when the user hits enter on
3862 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3865 if field in self._inherit_fields:
3866 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3868 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3870 def name_get(self, cr, user, ids, context=None):
3873 :param cr: database cursor
3874 :param user: current user id
3876 :param ids: list of ids
3877 :param context: context arguments, like lang, time zone
3878 :type context: dictionary
3879 :return: tuples with the text representation of requested objects for to-many relationships
3886 if isinstance(ids, (int, long)):
3888 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
3889 [self._rec_name], context, load='_classic_write')]
3891 # private implementation of name_search, allows passing a dedicated user for the name_get part to
3892 # solve some access rights issues
3893 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
3900 args += [(self._rec_name, operator, name)]
3901 ids = self.search(cr, user, args, limit=limit, context=context)
3902 res = self.name_get(cr, name_get_uid or user, ids, context)
3905 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
3908 :param cr: database cursor
3909 :param user: current user id
3910 :param name: object name to search
3911 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
3912 :param operator: operator for search criterion
3913 :param context: context arguments, like lang, time zone
3914 :type context: dictionary
3915 :param limit: optional max number of records to return
3916 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
3918 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
3919 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
3922 return self._name_search(cr, user, name, args, operator, context, limit)
3924 def copy_data(self, cr, uid, id, default=None, context=None):
3926 Copy given record's data with all its fields values
3928 :param cr: database cursor
3929 :param user: current user id
3930 :param id: id of the record to copy
3931 :param default: field values to override in the original values of the copied record
3932 :type default: dictionary
3933 :param context: context arguments, like lang, time zone
3934 :type context: dictionary
3935 :return: dictionary containing all the field values
3942 if 'state' not in default:
3943 if 'state' in self._defaults:
3944 if callable(self._defaults['state']):
3945 default['state'] = self._defaults['state'](self, cr, uid, context)
3947 default['state'] = self._defaults['state']
3949 context_wo_lang = context
3950 if 'lang' in context:
3951 del context_wo_lang['lang']
3952 data = self.read(cr, uid, [id], context=context_wo_lang)[0]
3954 fields = self.fields_get(cr, uid, context=context)
3956 ftype = fields[f]['type']
3958 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3962 data[f] = default[f]
3963 elif ftype == 'function':
3965 elif ftype == 'many2one':
3967 data[f] = data[f] and data[f][0]
3970 elif ftype in ('one2many', 'one2one'):
3972 rel = self.pool.get(fields[f]['relation'])
3974 # duplicate following the order of the ids
3975 # because we'll rely on it later for copying
3976 # translations in copy_translation()!
3978 for rel_id in data[f]:
3979 # the lines are first duplicated using the wrong (old)
3980 # parent but then are reassigned to the correct one thanks
3981 # to the (0, 0, ...)
3982 d = rel.copy_data(cr, uid, rel_id, context=context)
3983 res.append((0, 0, d))
3985 elif ftype == 'many2many':
3986 data[f] = [(6, 0, data[f])]
3990 # make sure we don't break the current parent_store structure and
3991 # force a clean recompute!
3992 for parent_column in ['parent_left', 'parent_right']:
3993 data.pop(parent_column, None)
3995 for v in self._inherits:
3996 del data[self._inherits[v]]
3999 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4000 trans_obj = self.pool.get('ir.translation')
4001 fields = self.fields_get(cr, uid, context=context)
4003 translation_records = []
4004 for field_name, field_def in fields.items():
4005 # we must recursively copy the translations for o2o and o2m
4006 if field_def['type'] in ('one2one', 'one2many'):
4007 target_obj = self.pool.get(field_def['relation'])
4008 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4009 # here we rely on the order of the ids to match the translations
4010 # as foreseen in copy_data()
4011 old_childs = sorted(old_record[field_name])
4012 new_childs = sorted(new_record[field_name])
4013 for (old_child, new_child) in zip(old_childs, new_childs):
4014 # recursive copy of translations here
4015 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4016 # and for translatable fields we keep them for copy
4017 elif field_def.get('translate'):
4019 if field_name in self._columns:
4020 trans_name = self._name + "," + field_name
4021 elif field_name in self._inherit_fields:
4022 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4024 trans_ids = trans_obj.search(cr, uid, [
4025 ('name', '=', trans_name),
4026 ('res_id','=', old_id)
4028 translation_records.extend(trans_obj.read(cr,uid,trans_ids,context=context))
4030 for record in translation_records:
4032 record['res_id'] = new_id
4033 trans_obj.create(cr, uid, record, context=context)
4036 def copy(self, cr, uid, id, default=None, context=None):
4038 Duplicate record with given id updating it with default values
4040 :param cr: database cursor
4041 :param uid: current user id
4042 :param id: id of the record to copy
4043 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4044 :type default: dictionary
4045 :param context: context arguments, like lang, time zone
4046 :type context: dictionary
4050 data = self.copy_data(cr, uid, id, default, context)
4051 new_id = self.create(cr, uid, data, context)
4052 self.copy_translations(cr, uid, id, new_id, context)
4055 def exists(self, cr, uid, ids, context=None):
4056 if type(ids) in (int,long):
4058 query = 'SELECT count(1) FROM "%s"' % (self._table)
4059 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4060 return cr.fetchone()[0] == len(ids)
4062 def check_recursion(self, cr, uid, ids, parent=None):
4064 Verifies that there is no loop in a hierarchical structure of records,
4065 by following the parent relationship using the **parent** field until a loop
4066 is detected or until a top-level record is found.
4068 :param cr: database cursor
4069 :param uid: current user id
4070 :param ids: list of ids of records to check
4071 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4072 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4076 parent = self._parent_name
4078 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4081 for i in range(0, len(ids), cr.IN_MAX):
4082 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4083 cr.execute(query, (tuple(sub_ids_parent),))
4084 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4085 ids_parent = ids_parent2
4086 for i in ids_parent:
4091 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4092 """Find out the XML ID of any database record, if there
4093 is one. This method works as a possible implementation
4094 for a function field, to be able to add it to any
4095 model object easily, referencing it as ``osv.osv.get_xml_id``.
4097 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4099 :return: the fully qualified XML ID of the given object,
4100 defaulting to an empty string when there's none
4101 (to be usable as a function field).
4103 result = dict.fromkeys(ids, '')
4104 model_data_obj = self.pool.get('ir.model.data')
4105 data_ids = model_data_obj.search(cr,uid,
4106 [('model','=',self._name),('res_id','in',ids)])
4107 data_results = model_data_obj.read(cr,uid,data_ids,
4108 ['name','module','res_id'])
4109 for record in data_results:
4110 result[record['res_id']] = '%(module)s.%(name)s' % record
4113 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: