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 ffields = 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 ffields += filter(lambda x: x[1]._classic_write, inherits)
194 # otherwise we fetch only that field
196 ffields = [(name, col)]
197 ids = filter(lambda id: name not in self._data[id], self._data.keys())
199 fffields = map(lambda x: x[0], ffields)
200 datas = self._table.read(self._cr, self._uid, ids, fffields, 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])
209 if f._type in self._fields_process:
211 d[n] = self._fields_process[f._type](d[n])
213 d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj)
217 # Where did those ids come from? Perhaps old entries in ir_model_dat?
218 self.__logger.warn("No datas found for ids %s in %s",
220 raise KeyError('Field %s not found in %s'%(name,self))
221 # create browse records for 'remote' objects
223 if len(str(data['id']).split('-')) > 1:
224 data['id'] = int(str(data['id']).split('-')[0])
227 if f._type in ('many2one', 'one2one'):
229 obj = self._table.pool.get(f._obj)
231 if type(data[n]) in (type([]),type( (1,) )):
236 # FIXME: this happen when a _inherits object
237 # overwrite a field of it parent. Need
238 # testing to be sure we got the right
239 # object and not the parent one.
240 if not isinstance(ids2, browse_record):
241 new_data[n] = browse_record(self._cr,
242 self._uid, ids2, obj, self._cache,
243 context=self._context,
244 list_class=self._list_class,
245 fields_process=self._fields_process)
249 new_data[n] = browse_null()
251 new_data[n] = browse_null()
252 elif f._type in ('one2many', 'many2many') and len(data[n]):
253 new_data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
254 elif f._type in ('reference'):
256 if isinstance(data[n], browse_record):
257 new_data[n] = data[n]
259 ref_obj, ref_id = data[n].split(',')
260 ref_id = long(ref_id)
261 obj = self._table.pool.get(ref_obj)
263 new_data[n] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
265 new_data[n] = browse_null()
267 new_data[n] = data[n]
268 self._data[data['id']].update(new_data)
270 if not name in self._data[self._id]:
271 #how did this happen?
272 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
273 "Ffields: %s, datas: %s"%(fffields, datas))
274 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
275 "Data: %s, Table: %s"%(self._data[self._id], self._table))
276 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
277 return self._data[self._id][name]
279 def __getattr__(self, name):
283 raise AttributeError(e)
285 def __contains__(self, name):
286 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
288 def __hasattr__(self, name):
295 return "browse_record(%s, %d)" % (self._table_name, self._id)
297 def __eq__(self, other):
298 if not isinstance(other, browse_record):
300 return (self._table_name, self._id) == (other._table_name, other._id)
302 def __ne__(self, other):
303 if not isinstance(other, browse_record):
305 return (self._table_name, self._id) != (other._table_name, other._id)
307 # we need to define __unicode__ even though we've already defined __str__
308 # because we have overridden __getattr__
309 def __unicode__(self):
310 return unicode(str(self))
313 return hash((self._table_name, self._id))
321 (type returned by postgres when the column was created, type expression to create the column)
325 fields.boolean: 'bool',
326 fields.integer: 'int4',
327 fields.integer_big: 'int8',
331 fields.datetime: 'timestamp',
332 fields.binary: 'bytea',
333 fields.many2one: 'int4',
335 if type(f) in type_dict:
336 f_type = (type_dict[type(f)], type_dict[type(f)])
337 elif isinstance(f, fields.float):
339 f_type = ('numeric', 'NUMERIC')
341 f_type = ('float8', 'DOUBLE PRECISION')
342 elif isinstance(f, (fields.char, fields.reference)):
343 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
344 elif isinstance(f, fields.selection):
345 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
346 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
347 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
350 f_size = getattr(f, 'size', None) or 16
353 f_type = ('int4', 'INTEGER')
355 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
356 elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict:
357 t = eval('fields.'+(f._type), globals())
358 f_type = (type_dict[t], type_dict[t])
359 elif isinstance(f, fields.function) and f._type == 'float':
361 f_type = ('numeric', 'NUMERIC')
363 f_type = ('float8', 'DOUBLE PRECISION')
364 elif isinstance(f, fields.function) and f._type == 'selection':
365 f_type = ('text', 'text')
366 elif isinstance(f, fields.function) and f._type == 'char':
367 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
369 logger = netsvc.Logger()
370 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
375 class orm_template(object):
381 _parent_name = 'parent_id'
382 _parent_store = False
383 _parent_order = False
393 CONCURRENCY_CHECK_FIELD = '__last_update'
394 def log(self, cr, uid, id, message, secondary=False, context=None):
395 return self.pool.get('res.log').create(cr, uid, {
397 'res_model': self._name,
398 'secondary': secondary,
403 def view_init(self, cr , uid , fields_list, context=None):
404 """Override this method to do specific things when a view on the object is opened."""
407 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
408 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
410 def _field_create(self, cr, context={}):
411 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
413 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
414 model_id = cr.fetchone()[0]
415 cr.execute("INSERT INTO ir_model (id,model, name, info,state) VALUES (%s, %s, %s, %s, %s)", (model_id, self._name, self._description, self.__doc__, 'base'))
417 model_id = cr.fetchone()[0]
418 if 'module' in context:
419 name_id = 'model_'+self._name.replace('.','_')
420 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module']))
422 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
423 (name_id, context['module'], 'ir.model', model_id)
428 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
430 for rec in cr.dictfetchall():
431 cols[rec['name']] = rec
433 for (k, f) in self._columns.items():
435 'model_id': model_id,
438 'field_description': f.string.replace("'", " "),
440 'relation': f._obj or '',
441 'view_load': (f.view_load and 1) or 0,
442 'select_level': tools.ustr(f.select or 0),
443 'readonly':(f.readonly and 1) or 0,
444 'required':(f.required and 1) or 0,
445 'selectable' : (f.selectable and 1) or 0,
446 'relation_field': (f._type=='one2many' and isinstance(f,fields.one2many)) and f._fields_id or '',
448 # When its a custom field,it does not contain f.select
449 if context.get('field_state','base') == 'manual':
450 if context.get('field_name','') == k:
451 vals['select_level'] = context.get('select','0')
452 #setting value to let the problem NOT occur next time
454 vals['select_level'] = cols[k]['select_level']
457 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
458 id = cr.fetchone()[0]
460 cr.execute("""INSERT INTO ir_model_fields (
461 id, model_id, model, name, field_description, ttype,
462 relation,view_load,state,select_level,relation_field
464 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
466 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
467 vals['relation'], bool(vals['view_load']), 'base',
468 vals['select_level'],vals['relation_field']
470 if 'module' in context:
471 name1 = 'field_' + self._table + '_' + k
472 cr.execute("select name from ir_model_data where name=%s", (name1,))
474 name1 = name1 + "_" + str(id)
475 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
476 (name1, context['module'], 'ir.model.fields', id)
479 for key, val in vals.items():
480 if cols[k][key] != vals[key]:
481 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
483 cr.execute("""UPDATE ir_model_fields SET
484 model_id=%s, field_description=%s, ttype=%s, relation=%s,
485 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
487 model=%s AND name=%s""", (
488 vals['model_id'], vals['field_description'], vals['ttype'],
489 vals['relation'], bool(vals['view_load']),
490 vals['select_level'], bool(vals['readonly']),bool(vals['required']),bool(vals['selectable']),vals['relation_field'],vals['model'], vals['name']
495 def _auto_init(self, cr, context={}):
496 self._field_create(cr, context)
498 def __init__(self, cr):
499 if not self._name and not hasattr(self, '_inherit'):
500 name = type(self).__name__.split('.')[0]
501 msg = "The class %s has to have a _name attribute" % name
503 logger = netsvc.Logger()
504 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg )
505 raise except_orm('ValueError', msg )
507 if not self._description:
508 self._description = self._name
510 self._table = self._name.replace('.', '_')
512 def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
514 Fetch records as objects allowing to use dot notation to browse fields and relations
516 :param cr: database cursor
517 :param user: current user id
518 :param select: id or list of ids
519 :param context: context arguments, like lang, time zone
520 :rtype: object or list of objects requested
525 self._list_class = list_class or browse_record_list
527 # need to accepts ints and longs because ids coming from a method
528 # launched by button in the interface have a type long...
529 if isinstance(select, (int, long)):
530 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
531 elif isinstance(select, list):
532 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)
536 def __export_row(self, cr, uid, row, fields, context=None):
540 def check_type(field_type):
541 if field_type == 'float':
543 elif field_type == 'integer':
545 elif field_type == 'boolean':
549 def selection_field(in_field):
550 col_obj = self.pool.get(in_field.keys()[0])
551 if f[i] in col_obj._columns.keys():
552 return col_obj._columns[f[i]]
553 elif f[i] in col_obj._inherits.keys():
554 selection_field(col_obj._inherits)
560 data = map(lambda x: '', range(len(fields)))
562 for fpos in range(len(fields)):
571 model_data = self.pool.get('ir.model.data')
572 data_ids = model_data.search(cr, uid, [('model','=',r._table_name),('res_id','=',r['id'])])
574 d = model_data.read(cr, uid, data_ids, ['name','module'])[0]
576 r = '%s.%s'%(d['module'],d['name'])
583 # To display external name of selection field when its exported
584 if not context.get('import_comp',False):# Allow external name only if its not import compatible
586 if f[i] in self._columns.keys():
587 cols = self._columns[f[i]]
588 elif f[i] in self._inherit_fields.keys():
589 cols = selection_field(self._inherits)
590 if cols and cols._type == 'selection':
591 sel_list = cols.selection
592 if r and type(sel_list) == type([]):
593 r = [x[1] for x in sel_list if r==x[0]]
594 r = r and r[0] or False
596 if f[i] in self._columns:
597 r = check_type(self._columns[f[i]]._type)
598 elif f[i] in self._inherit_fields:
599 r = check_type(self._inherit_fields[f[i]][2]._type)
602 if isinstance(r, (browse_record_list, list)):
604 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
610 lines2 = self.__export_row(cr, uid, row2, fields2,
613 for fpos2 in range(len(fields)):
614 if lines2 and lines2[0][fpos2]:
615 data[fpos2] = lines2[0][fpos2]
619 if isinstance(rr.name, browse_record):
621 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
622 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
623 dt += tools.ustr(rr_name or '') + ','
633 if isinstance(r, browse_record):
634 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
635 r = r and r[0] and r[0][1] or ''
636 data[fpos] = tools.ustr(r or '')
637 return [data] + lines
639 def export_data(self, cr, uid, ids, fields_to_export, context=None):
641 Export fields for selected objects
643 :param cr: database cursor
644 :param uid: current user id
645 :param ids: list of ids
646 :param fields_to_export: list of fields
647 :param context: context arguments, like lang, time zone, may contain import_comp(default: False) to make exported data compatible with import_data()
648 :rtype: dictionary with a *datas* matrix
650 This method is used when exporting data via client menu
655 imp_comp = context.get('import_comp',False)
656 cols = self._columns.copy()
657 for f in self._inherit_fields:
658 cols.update({f: self._inherit_fields[f][2]})
659 fields_to_export = map(lambda x: x.split('/'), fields_to_export)
660 fields_export = fields_to_export+[]
663 for field in fields_export:
664 if imp_comp and len(field)>1:
665 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
666 elif len (field) <=1:
667 if imp_comp and cols.get(field and field[0],False):
668 if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
669 or isinstance(cols[field[0]], fields.related)\
670 or isinstance(cols[field[0]], fields.one2many)):
671 warning_fields.append('/'.join(map(lambda x:x in cols and cols[x].string or x,field)))
673 if imp_comp and len(warning_fields):
674 warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' %('\n'.join(warning_fields))
676 return {'warning' : warning}
677 for row in self.browse(cr, uid, ids, context):
678 datas += self.__export_row(cr, uid, row, fields_to_export, context)
679 return {'datas':datas}
681 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
683 Import given data in given module
685 :param cr: database cursor
686 :param uid: current user id
687 :param ids: list of ids
688 :param fields: list of fields
689 :param data: data to import
690 :param mode: 'init' or 'update' for record creation
691 :param current_module: module name
692 :param noupdate: flag for record creation
693 :param context: context arguments, like lang, time zone,
694 :param filename: optional file to store partial import state for recovery
697 This method is used when importing data via client menu
702 fields = map(lambda x: x.split('/'), fields)
703 logger = netsvc.Logger()
704 ir_model_data_obj = self.pool.get('ir.model.data')
706 def _check_db_id(self, model_name, db_id):
707 obj_model = self.pool.get(model_name)
708 ids = obj_model.search(cr, uid, [('id','=',int(db_id))])
710 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
713 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
714 line = datas[position]
723 ir_model_data_obj = self.pool.get('ir.model.data')
725 # Import normal fields
727 for i in range(len(fields)):
729 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
734 if prefix and not prefix[0] in field:
737 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
741 field_name = field[0].split(':')[0]
742 model_rel = fields_def[field_name]['relation']
744 if fields_def[field[len(prefix)][:-6]]['type']=='many2many':
746 for db_id in line[i].split(config.get('csv_internal_sep')):
748 _check_db_id(self, model_rel, db_id)
751 warning += [tools.exception_to_unicode(e)]
752 logger.notifyChannel("import", netsvc.LOG_ERROR,
753 tools.exception_to_unicode(e))
755 res = [(6, 0, res_id)]
758 _check_db_id(self, model_rel, line[i])
761 warning += [tools.exception_to_unicode(e)]
762 logger.notifyChannel("import", netsvc.LOG_ERROR,
763 tools.exception_to_unicode(e))
764 row[field_name] = res or False
767 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
770 if fields_def[field[len(prefix)][:-3]]['type']=='many2many':
772 for word in line[i].split(config.get('csv_internal_sep')):
774 module, xml_id = word.rsplit('.', 1)
776 module, xml_id = current_module, word
777 id = ir_model_data_obj._get_id(cr, uid, module,
779 res_id2 = ir_model_data_obj.read(cr, uid, [id],
780 ['res_id'])[0]['res_id']
782 res_id.append(res_id2)
784 res_id = [(6, 0, res_id)]
787 module, xml_id = line[i].rsplit('.', 1)
789 module, xml_id = current_module, line[i]
790 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
791 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
793 res_id = ir_model_data[0]['res_id']
795 raise ValueError('No references to %s.%s' % (module, xml_id))
796 row[field[-1][:-3]] = res_id or False
798 if (len(field) == len(prefix)+1) and \
799 len(field[len(prefix)].split(':lang=')) == 2:
800 f, lang = field[len(prefix)].split(':lang=')
801 translate.setdefault(lang, {})[f]=line[i] or False
803 if (len(field) == len(prefix)+1) and \
804 (prefix == field[0:len(prefix)]):
805 if field[len(prefix)] == "id":
808 is_xml_id = data_id = line[i]
809 d = data_id.split('.')
810 module = len(d)>1 and d[0] or ''
811 name = len(d)>1 and d[1] or d[0]
812 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('name','=',name)])
814 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
816 if is_db_id and not db_id:
817 data_ids = ir_model_data_obj.search(cr, uid, [('module','=',module),('model','=',model_name),('res_id','=',is_db_id)])
818 if not len(data_ids):
819 ir_model_data_obj.create(cr, uid, {'module':module, 'model':model_name, 'name':name, 'res_id':is_db_id})
821 if is_db_id and int(db_id) != int(is_db_id):
822 warning += [_("Id is not the same than existing one: %s")%(is_db_id)]
823 logger.notifyChannel("import", netsvc.LOG_ERROR,
824 _("Id is not the same than existing one: %s")%(is_db_id))
827 if field[len(prefix)] == "db_id":
830 _check_db_id(self, model_name, line[i])
831 data_res_id = is_db_id = int(line[i])
833 warning += [tools.exception_to_unicode(e)]
834 logger.notifyChannel("import", netsvc.LOG_ERROR,
835 tools.exception_to_unicode(e))
837 data_ids = ir_model_data_obj.search(cr, uid, [('model','=',model_name),('res_id','=',line[i])])
839 d = ir_model_data_obj.read(cr, uid, data_ids, ['name','module'])[0]
842 data_id = '%s.%s'%(d['module'],d['name'])
845 if is_xml_id and not data_id:
847 if is_xml_id and is_xml_id!=data_id:
848 warning += [_("Id is not the same than existing one: %s")%(line[i])]
849 logger.notifyChannel("import", netsvc.LOG_ERROR,
850 _("Id is not the same than existing one: %s")%(line[i]))
853 if fields_def[field[len(prefix)]]['type'] == 'integer':
854 res = line[i] and int(line[i])
855 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
856 res = line[i].lower() not in ('0', 'false', 'off')
857 elif fields_def[field[len(prefix)]]['type'] == 'float':
858 res = line[i] and float(line[i])
859 elif fields_def[field[len(prefix)]]['type'] == 'selection':
861 if isinstance(fields_def[field[len(prefix)]]['selection'],
863 sel = fields_def[field[len(prefix)]]['selection']
865 sel = fields_def[field[len(prefix)]]['selection'](self,
868 if line[i] in [tools.ustr(key),tools.ustr(val)]: #Acepting key or value for selection field
871 if line[i] and not res:
872 logger.notifyChannel("import", netsvc.LOG_WARNING,
873 _("key '%s' not found in selection field '%s'") % \
874 (line[i], field[len(prefix)]))
876 warning += [_("Key/value '%s' not found in selection field '%s'")%(line[i],field[len(prefix)])]
878 elif fields_def[field[len(prefix)]]['type']=='many2one':
881 relation = fields_def[field[len(prefix)]]['relation']
882 res2 = self.pool.get(relation).name_search(cr, uid,
883 line[i], [], operator='=', context=context)
884 res = (res2 and res2[0][0]) or False
886 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
887 logger.notifyChannel("import", netsvc.LOG_WARNING,
888 _("Relation not found: %s on '%s'")%(line[i],relation))
889 elif fields_def[field[len(prefix)]]['type']=='many2many':
892 relation = fields_def[field[len(prefix)]]['relation']
893 for word in line[i].split(config.get('csv_internal_sep')):
894 res2 = self.pool.get(relation).name_search(cr,
895 uid, word, [], operator='=', context=context)
896 res3 = (res2 and res2[0][0]) or False
898 warning += [_("Relation not found: %s on '%s'")%(line[i],relation)]
899 logger.notifyChannel("import",
901 _("Relation not found: %s on '%s'")%(line[i],relation))
907 res = line[i] or False
908 row[field[len(prefix)]] = res
909 elif (prefix==field[0:len(prefix)]):
910 if field[0] not in todo:
911 todo.append(field[len(prefix)])
913 # Import one2many, many2many fields
917 relation_obj = self.pool.get(fields_def[field]['relation'])
918 newfd = relation_obj.fields_get(
919 cr, uid, context=context)
920 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
921 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
922 nbrmax = max(nbrmax, max2)
923 warning = warning + w2
924 reduce(lambda x, y: x and y, newrow)
925 row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
926 [(0, 0, newrow)]) or []
928 while (position+i)<len(datas):
930 for j in range(len(fields)):
932 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
937 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
938 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
940 if reduce(lambda x, y: x or y, newrow.values()):
941 row[field].append((0, 0, newrow))
943 nbrmax = max(nbrmax, i)
946 for i in range(max(nbrmax, 1)):
949 result = (row, nbrmax, warning, translate, data_id, data_res_id)
952 fields_def = self.fields_get(cr, uid, context=context)
955 initial_size = len(datas)
956 if config.get('import_partial', False) and filename:
957 data = pickle.load(file(config.get('import_partial')))
958 original_value = data.get(filename, 0)
964 (res, other, warning, translate, data_id, res_id) = \
965 process_liness(self, datas, [], current_module, self._name, fields_def)
968 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
971 id = ir_model_data_obj._update(cr, uid, self._name,
972 current_module, res, xml_id=data_id, mode=mode,
973 noupdate=noupdate, res_id=res_id, context=context)
978 if isinstance(e,psycopg2.IntegrityError):
979 msg= _('Insertion Failed! ')
980 for key in self.pool._sql_error.keys():
982 msg = self.pool._sql_error[key]
984 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
985 if isinstance(e, osv.orm.except_orm ):
986 msg = _('Insertion Failed! ' + e[1])
987 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '' )
988 #Raising Uncaught exception
989 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '' )
991 for lang in translate:
992 context2 = context.copy()
993 context2['lang'] = lang
994 self.write(cr, uid, [id], translate[lang], context2)
995 if config.get('import_partial', False) and filename and (not (counter%100)) :
996 data = pickle.load(file(config.get('import_partial')))
997 data[filename] = initial_size - len(datas) + original_value
998 pickle.dump(data, file(config.get('import_partial'),'wb'))
999 if context.get('defer_parent_store_computation'):
1000 self._parent_store_compute(cr)
1003 #except Exception, e:
1004 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1007 # return (-1, res, e[0], warning)
1009 # return (-1, res, e[0], '')
1012 # TODO: Send a request with the result and multi-thread !
1014 if context.get('defer_parent_store_computation'):
1015 self._parent_store_compute(cr)
1016 return (done, 0, 0, 0)
1018 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1019 raise NotImplementedError(_('The read method is not implemented on this object !'))
1021 def get_invalid_fields(self,cr,uid):
1022 return list(self._invalids)
1024 def _validate(self, cr, uid, ids, context=None):
1025 context = context or {}
1026 lng = context.get('lang', False) or 'en_US'
1027 trans = self.pool.get('ir.translation')
1029 for constraint in self._constraints:
1030 fun, msg, fields = constraint
1031 if not fun(self, cr, uid, ids):
1032 # Check presence of __call__ directly instead of using
1033 # callable() because it will be deprecated as of Python 3.0
1034 if hasattr(msg, '__call__'):
1035 txt_msg, params = msg(self, cr, uid, ids)
1036 tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg
1037 translated_msg = tmp_msg % params
1039 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1041 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1043 self._invalids.update(fields)
1046 raise except_orm('ValidateError', '\n'.join(error_msgs))
1048 self._invalids.clear()
1050 def default_get(self, cr, uid, fields_list, context=None):
1052 Set default values for the object's fields.
1054 :param fields_list: fields for which the object doesn't have any value yet, and default values need to be provided.
1055 If fields outside this list are returned, the user-provided values will be overwritten.
1056 :rtype: a dict of {field_name:default_value}
1061 def perm_read(self, cr, user, ids, context=None, details=True):
1062 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1064 def unlink(self, cr, uid, ids, context=None):
1065 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1067 def write(self, cr, user, ids, vals, context=None):
1068 raise NotImplementedError(_('The write method is not implemented on this object !'))
1070 def create(self, cr, user, vals, context=None):
1071 raise NotImplementedError(_('The create method is not implemented on this object !'))
1073 # returns the definition of each field in the object
1074 # the optional fields parameter can limit the result to some fields
1075 def fields_get_keys(self, cr, user, context=None, read_access=True):
1078 res = self._columns.keys()
1079 for parent in self._inherits:
1080 res.extend(self.pool.get(parent).fields_get_keys(cr, user, fields, context))
1083 def fields_get(self, cr, user, fields=None, context=None, read_access=True):
1087 translation_obj = self.pool.get('ir.translation')
1088 model_access_obj = self.pool.get('ir.model.access')
1089 for parent in self._inherits:
1090 res.update(self.pool.get(parent).fields_get(cr, user, fields, context))
1092 if self._columns.keys():
1093 for f in self._columns.keys():
1094 if fields and f not in fields:
1096 res[f] = {'type': self._columns[f]._type}
1097 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1098 'change_default', 'translate', 'help', 'select', 'selectable'):
1099 if getattr(self._columns[f], arg):
1100 res[f][arg] = getattr(self._columns[f], arg)
1102 res[f]['readonly'] = True
1103 res[f]['states'] = {}
1104 for arg in ('digits', 'invisible','filters'):
1105 if getattr(self._columns[f], arg, None):
1106 res[f][arg] = getattr(self._columns[f], arg)
1108 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', self._columns[f].string)
1110 res[f]['string'] = res_trans
1111 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1113 res[f]['help'] = help_trans
1115 if hasattr(self._columns[f], 'selection'):
1116 if isinstance(self._columns[f].selection, (tuple, list)):
1117 sel = self._columns[f].selection
1118 # translate each selection option
1120 for (key, val) in sel:
1123 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1124 sel2.append((key, val2 or val))
1126 res[f]['selection'] = sel
1128 # call the 'dynamic selection' function
1129 res[f]['selection'] = self._columns[f].selection(self, cr,
1131 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1132 res[f]['relation'] = self._columns[f]._obj
1133 res[f]['domain'] = self._columns[f]._domain
1134 res[f]['context'] = self._columns[f]._context
1136 #TODO : read the fields from the database
1140 # filter out fields which aren't in the fields list
1141 for r in res.keys():
1147 # Overload this method if you need a window title which depends on the context
1149 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1152 def __view_look_dom(self, cr, user, node, view_id, context=None):
1160 if isinstance(s, unicode):
1161 return s.encode('utf8')
1164 # return True if node can be displayed to current user
1165 def check_group(node):
1166 if node.get('groups'):
1167 groups = node.get('groups').split(',')
1168 access_pool = self.pool.get('ir.model.access')
1169 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1171 node.set('invisible', '1')
1172 if 'attrs' in node.attrib:
1173 del(node.attrib['attrs']) #avoid making field visible later
1174 del(node.attrib['groups'])
1179 if node.tag in ('field', 'node', 'arrow'):
1180 if node.get('object'):
1185 if f.tag in ('field'):
1186 xml += etree.tostring(f, encoding="utf-8")
1188 new_xml = etree.fromstring(encode(xml))
1189 ctx = context.copy()
1190 ctx['base_model_name'] = self._name
1191 xarch, xfields = self.pool.get(node.get('object',False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1192 views[str(f.tag)] = {
1196 attrs = {'views': views}
1198 fields = views.get('field',False) and views['field'].get('fields',False)
1199 if node.get('name'):
1202 if node.get('name') in self._columns:
1203 column = self._columns[node.get('name')]
1205 column = self._inherit_fields[node.get('name')][2]
1210 relation = self.pool.get(column._obj)
1215 if f.tag in ('form', 'tree', 'graph'):
1217 ctx = context.copy()
1218 ctx['base_model_name'] = self._name
1219 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1220 views[str(f.tag)] = {
1224 attrs = {'views': views}
1225 if node.get('widget') and node.get('widget') == 'selection':
1226 if not check_group(node):
1227 name = node.get('name')
1228 default = self.default_get(cr, user, [name], context=context).get(name)
1230 attrs['selection'] = relation.name_get(cr, 1, [default], context=context)
1232 attrs['selection'] = []
1233 # We can not use the 'string' domain has it is defined according to the record !
1235 # If domain and context are strings, we keep them for client-side, otherwise
1236 # we evaluate them server-side to consider them when generating the list of
1238 # TODO: find a way to remove this hack, by allow dynamic domains
1240 if column._domain and not isinstance(column._domain, basestring):
1241 dom = column._domain
1242 dom += eval(node.get('domain','[]'), {'uid':user, 'time':time})
1243 search_context = dict(context)
1244 if column._context and not isinstance(column._context, basestring):
1245 search_context.update(column._context)
1246 attrs['selection'] = relation._name_search(cr, 1, '', dom, context=search_context, limit=None, name_get_uid=1)
1247 if (node.get('required') and not int(node.get('required'))) or not column.required:
1248 attrs['selection'].append((False,''))
1249 fields[node.get('name')] = attrs
1251 elif node.tag in ('form', 'tree'):
1252 result = self.view_header_get(cr, user, False, node.tag, context)
1254 node.set('string', result)
1256 elif node.tag == 'calendar':
1257 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1258 if node.get(additional_field):
1259 fields[node.get(additional_field)] = {}
1261 if 'groups' in node.attrib:
1265 if ('lang' in context) and not result:
1266 if node.get('string'):
1267 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string').encode('utf8'))
1268 if not trans and ('base_model_name' in context):
1269 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string').encode('utf8'))
1271 node.set('string', trans)
1273 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum').encode('utf8'))
1275 node.set('sum', trans)
1279 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1283 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1284 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1286 rolesobj = self.pool.get('res.roles')
1287 usersobj = self.pool.get('res.users')
1289 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1290 for button in buttons:
1292 if user != 1: # admin user has all roles
1293 user_roles = usersobj.read(cr, user, [user], ['roles_id'])[0]['roles_id']
1294 # TODO handle the case of more than one workflow for a model
1295 cr.execute("""SELECT DISTINCT t.role_id
1297 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1298 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1301 """, (self._name, button.get('name'),))
1302 roles = cr.fetchall()
1304 # draft -> valid = signal_next (role X)
1305 # draft -> cancel = signal_cancel (no role)
1307 # valid -> running = signal_next (role Y)
1308 # valid -> cancel = signal_cancel (role Z)
1310 # running -> done = signal_next (role Z)
1311 # running -> cancel = signal_cancel (role Z)
1313 # As we don't know the object state, in this scenario,
1314 # the button "signal_cancel" will be always shown as there is no restriction to cancel in draft
1315 # the button "signal_next" will be show if the user has any of the roles (X Y or Z)
1316 # The verification will be made later in workflow process...
1318 can_click = any((not role) or rolesobj.check(cr, user, user_roles, role) for (role,) in roles)
1320 button.set('readonly', str(int(not can_click)))
1322 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1325 if node.tag=='diagram':
1326 if node.getchildren()[0].tag=='node':
1327 node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1328 if node.getchildren()[1].tag=='arrow':
1329 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1330 for key,value in node_fields.items():
1332 for key,value in arrow_fields.items():
1335 fields = self.fields_get(cr, user, fields_def.keys(), context)
1336 for field in fields_def:
1338 # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1339 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1340 elif field in fields:
1341 fields[field].update(fields_def[field])
1343 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))
1344 res = cr.fetchall()[:]
1346 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1347 msg = "\n * ".join([r[0] for r in res])
1348 msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
1349 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1350 raise except_orm('View error', msg)
1353 def __get_default_calendar_view(self):
1354 """Generate a default calendar view (For internal use only).
1357 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1358 '<calendar string="%s"') % (self._description)
1360 if (self._date_name not in self._columns):
1362 for dt in ['date','date_start','x_date','x_date_start']:
1363 if dt in self._columns:
1364 self._date_name = dt
1369 raise except_orm(_('Invalid Object Architecture!'),_("Insufficient fields for Calendar View!"))
1372 arch +=' date_start="%s"' % (self._date_name)
1374 for color in ["user_id","partner_id","x_user_id","x_partner_id"]:
1375 if color in self._columns:
1376 arch += ' color="' + color + '"'
1379 dt_stop_flag = False
1381 for dt_stop in ["date_stop","date_end","x_date_stop","x_date_end"]:
1382 if dt_stop in self._columns:
1383 arch += ' date_stop="' + dt_stop + '"'
1387 if not dt_stop_flag:
1388 for dt_delay in ["date_delay","planned_hours","x_date_delay","x_planned_hours"]:
1389 if dt_delay in self._columns:
1390 arch += ' date_delay="' + dt_delay + '"'
1394 ' <field name="%s"/>\n'
1395 '</calendar>') % (self._rec_name)
1399 def __get_default_search_view(self, cr, uid, context={}):
1402 if isinstance(s, unicode):
1403 return s.encode('utf8')
1406 view = self.fields_view_get(cr, uid, False, 'form', context)
1408 root = etree.fromstring(encode(view['arch']))
1409 res = etree.XML("<search string='%s'></search>" % root.get("string", ""))
1410 node = etree.Element("group")
1413 fields = root.xpath("//field[@select=1]")
1414 for field in fields:
1417 return etree.tostring(res, encoding="utf-8").replace('\t', '')
1420 # if view_id, view_type is not required
1422 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1424 Get the detailed composition of the requested view like fields, model, view architecture
1426 :param cr: database cursor
1427 :param user: current user id
1428 :param view_id: id of the view or None
1429 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1430 :param context: context arguments, like lang, time zone
1431 :param toolbar: true to include contextual actions
1432 :param submenu: example (portal_project module)
1433 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1434 :raise AttributeError:
1435 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1436 * if some tag other than 'position' is found in parent view
1437 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1444 if isinstance(s, unicode):
1445 return s.encode('utf8')
1448 def _inherit_apply(src, inherit):
1449 def _find(node, node2):
1450 if node2.tag == 'xpath':
1451 res = node.xpath(node2.get('expr'))
1457 for n in node.getiterator(node2.tag):
1459 for attr in node2.attrib:
1460 if attr == 'position':
1463 if n.get(attr) == node2.get(attr):
1470 # End: _find(node, node2)
1472 doc_dest = etree.fromstring(encode(inherit))
1473 toparse = [ doc_dest ]
1476 node2 = toparse.pop(0)
1477 if node2.tag == 'data':
1478 toparse += [ c for c in doc_dest ]
1480 node = _find(src, node2)
1481 if node is not None:
1483 if node2.get('position'):
1484 pos = node2.get('position')
1485 if pos == 'replace':
1486 parent = node.getparent()
1488 src = copy.deepcopy(node2[0])
1491 node.addprevious(child)
1492 node.getparent().remove(node)
1493 elif pos == 'attributes':
1494 for child in node2.getiterator('attribute'):
1495 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1497 node.set(attribute[0], attribute[1])
1499 del(node.attrib[attribute[0]])
1501 sib = node.getnext()
1505 elif pos == 'after':
1509 sib.addprevious(child)
1510 elif pos == 'before':
1511 node.addprevious(child)
1513 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1516 ' %s="%s"' % (attr, node2.get(attr))
1517 for attr in node2.attrib
1518 if attr != 'position'
1520 tag = "<%s%s>" % (node2.tag, attrs)
1521 raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1523 # End: _inherit_apply(src, inherit)
1525 result = {'type': view_type, 'model': self._name}
1531 view_ref = context.get(view_type + '_view_ref', False)
1534 module, view_ref = view_ref.split('.', 1)
1535 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1536 view_ref_res = cr.fetchone()
1538 view_id = view_ref_res[0]
1541 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1544 query += " AND model=%s"
1545 params += (self._name,)
1546 cr.execute(query, params)
1548 cr.execute('''SELECT
1549 arch,name,field_parent,id,type,inherit_id
1556 ORDER BY priority''', (self._name, view_type))
1557 sql_res = cr.fetchone()
1563 view_id = ok or sql_res[3]
1566 # if a view was found
1568 result['type'] = sql_res[4]
1569 result['view_id'] = sql_res[3]
1570 result['arch'] = sql_res[0]
1572 def _inherit_apply_rec(result, inherit_id):
1573 # get all views which inherit from (ie modify) this view
1574 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1575 sql_inherit = cr.fetchall()
1576 for (inherit, id) in sql_inherit:
1577 result = _inherit_apply(result, inherit)
1578 result = _inherit_apply_rec(result, id)
1581 inherit_result = etree.fromstring(encode(result['arch']))
1582 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1584 result['name'] = sql_res[1]
1585 result['field_parent'] = sql_res[2] or False
1588 # otherwise, build some kind of default view
1589 if view_type == 'form':
1590 res = self.fields_get(cr, user, context=context)
1591 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1592 '<form string="%s">' % (self._description,)
1594 if res[x]['type'] not in ('one2many', 'many2many'):
1595 xml += '<field name="%s"/>' % (x,)
1596 if res[x]['type'] == 'text':
1600 elif view_type == 'tree':
1601 _rec_name = self._rec_name
1602 if _rec_name not in self._columns:
1603 _rec_name = self._columns.keys()[0]
1604 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1605 '<tree string="%s"><field name="%s"/></tree>' \
1606 % (self._description, self._rec_name)
1608 elif view_type == 'calendar':
1609 xml = self.__get_default_calendar_view()
1611 elif view_type == 'search':
1612 xml = self.__get_default_search_view(cr, user, context)
1615 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1616 raise except_orm(_('Invalid Architecture!'),_("There is no view of type '%s' defined for the structure!") % view_type)
1617 result['arch'] = etree.fromstring(encode(xml))
1618 result['name'] = 'default'
1619 result['field_parent'] = False
1620 result['view_id'] = 0
1622 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1623 result['arch'] = xarch
1624 result['fields'] = xfields
1627 if context and context.get('active_id',False):
1628 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1630 act_id = data_menu.id
1632 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1633 result['submenu'] = getattr(data_action,'menus', False)
1637 for key in ('report_sxw_content', 'report_rml_content',
1638 'report_sxw', 'report_rml',
1639 'report_sxw_content_data', 'report_rml_content_data'):
1643 ir_values_obj = self.pool.get('ir.values')
1644 resprint = ir_values_obj.get(cr, user, 'action',
1645 'client_print_multi', [(self._name, False)], False,
1647 resaction = ir_values_obj.get(cr, user, 'action',
1648 'client_action_multi', [(self._name, False)], False,
1651 resrelate = ir_values_obj.get(cr, user, 'action',
1652 'client_action_relate', [(self._name, False)], False,
1654 resprint = map(clean, resprint)
1655 resaction = map(clean, resaction)
1656 resaction = filter(lambda x: not x.get('multi', False), resaction)
1657 resprint = filter(lambda x: not x.get('multi', False), resprint)
1658 resrelate = map(lambda x: x[2], resrelate)
1660 for x in resprint+resaction+resrelate:
1661 x['string'] = x['name']
1663 result['toolbar'] = {
1665 'action': resaction,
1668 if result['type']=='form' and result['arch'].count("default_focus")>1:
1669 msg = "Form View contain more than one default_focus attribute"
1670 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1671 raise except_orm('View Error !',msg)
1674 _view_look_dom_arch = __view_look_dom_arch
1676 def search_count(self, cr, user, args, context=None):
1679 res = self.search(cr, user, args, context=context, count=True)
1680 if isinstance(res, list):
1684 def search(self, cr, user, args, offset=0, limit=None, order=None,
1685 context=None, count=False):
1686 raise NotImplementedError(_('The search method is not implemented on this object !'))
1688 def name_get(self, cr, user, ids, context=None):
1689 raise NotImplementedError(_('The name_get method is not implemented on this object !'))
1691 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1692 raise NotImplementedError(_('The name_search method is not implemented on this object !'))
1694 def copy(self, cr, uid, id, default=None, context=None):
1695 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1697 def exists(self, cr, uid, id, context=None):
1698 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1700 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1703 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1705 fields = self._columns.keys() + self._inherit_fields.keys()
1706 #FIXME: collect all calls to _get_source into one SQL call.
1708 res[lang] = {'code': lang}
1710 if f in self._columns:
1711 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1713 res[lang][f] = res_trans
1715 res[lang][f] = self._columns[f].string
1716 for table in self._inherits:
1717 cols = intersect(self._inherit_fields.keys(), fields)
1718 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1721 res[lang]['code'] = lang
1722 for f in res2[lang]:
1723 res[lang][f] = res2[lang][f]
1726 def write_string(self, cr, uid, id, langs, vals, context=None):
1727 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1728 #FIXME: try to only call the translation in one SQL
1731 if field in self._columns:
1732 src = self._columns[field].string
1733 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1734 for table in self._inherits:
1735 cols = intersect(self._inherit_fields.keys(), vals)
1737 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1740 def _check_removed_columns(self, cr, log=False):
1741 raise NotImplementedError()
1743 class orm_memory(orm_template):
1744 _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']
1745 _inherit_fields = {}
1750 def __init__(self, cr):
1751 super(orm_memory, self).__init__(cr)
1755 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1757 def _check_access(self, uid, object_id, mode):
1758 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1759 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects' % mode.capitalize())
1761 def vaccum(self, cr, uid):
1763 if self.check_id % self._check_time:
1766 max = time.time() - self._max_hours * 60 * 60
1767 for id in self.datas:
1768 if self.datas[id]['internal.date_access'] < max:
1770 self.unlink(cr, uid, tounlink)
1771 if len(self.datas)>self._max_count:
1772 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1774 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1775 self.unlink(cr, uid, ids)
1778 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1781 if not fields_to_read:
1782 fields_to_read = self._columns.keys()
1786 if isinstance(ids, (int, long)):
1790 for f in fields_to_read:
1791 record = self.datas.get(id)
1793 self._check_access(user, id, 'read')
1794 r[f] = record.get(f, False)
1795 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1798 if id in self.datas:
1799 self.datas[id]['internal.date_access'] = time.time()
1800 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1801 for f in fields_post:
1802 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1803 for record in result:
1804 record[f] = res2[record['id']]
1805 if isinstance(ids_orig, (int, long)):
1809 def write(self, cr, user, ids, vals, context=None):
1815 if self._columns[field]._classic_write:
1816 vals2[field] = vals[field]
1818 upd_todo.append(field)
1819 for object_id in ids:
1820 self._check_access(user, object_id, mode='write')
1821 self.datas[object_id].update(vals2)
1822 self.datas[object_id]['internal.date_access'] = time.time()
1823 for field in upd_todo:
1824 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
1825 self._validate(cr, user, [object_id], context)
1826 wf_service = netsvc.LocalService("workflow")
1827 wf_service.trg_write(user, self._name, object_id, cr)
1830 def create(self, cr, user, vals, context=None):
1831 self.vaccum(cr, user)
1833 id_new = self.next_id
1835 for f in self._columns.keys():
1839 vals.update(self.default_get(cr, user, default, context))
1843 if self._columns[field]._classic_write:
1844 vals2[field] = vals[field]
1846 upd_todo.append(field)
1847 self.datas[id_new] = vals2
1848 self.datas[id_new]['internal.date_access'] = time.time()
1849 self.datas[id_new]['internal.create_uid'] = user
1851 for field in upd_todo:
1852 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1853 self._validate(cr, user, [id_new], context)
1854 if self._log_create and not (context and context.get('no_store_function', False)):
1855 message = self._description + \
1857 self.name_get(cr, user, [id_new], context=context)[0][1] + \
1859 self.log(cr, user, id_new, message, True, context=context)
1860 wf_service = netsvc.LocalService("workflow")
1861 wf_service.trg_create(user, self._name, id_new, cr)
1864 def default_get(self, cr, uid, fields_list, context=None):
1865 self.view_init(cr, uid, fields_list, context)
1869 # get the default values for the inherited fields
1870 for f in fields_list:
1871 if f in self._defaults:
1872 if callable(self._defaults[f]):
1873 value[f] = self._defaults[f](self, cr, uid, context)
1875 value[f] = self._defaults[f]
1877 fld_def = ((f in self._columns) and self._columns[f]) \
1878 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1881 # get the default values set by the user and override the default
1882 # values defined in the object
1883 ir_values_obj = self.pool.get('ir.values')
1884 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1885 for id, field, field_value in res:
1886 if field in fields_list:
1887 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1888 if fld_def._type in ('many2one', 'one2one'):
1889 obj = self.pool.get(fld_def._obj)
1890 if not obj.search(cr, uid, [('id', '=', field_value)]):
1892 if fld_def._type in ('many2many'):
1893 obj = self.pool.get(fld_def._obj)
1895 for i in range(len(field_value)):
1896 if not obj.search(cr, uid, [('id', '=',
1899 field_value2.append(field_value[i])
1900 field_value = field_value2
1901 if fld_def._type in ('one2many'):
1902 obj = self.pool.get(fld_def._obj)
1904 for i in range(len(field_value)):
1905 field_value2.append({})
1906 for field2 in field_value[i]:
1907 if obj._columns[field2]._type in ('many2one', 'one2one'):
1908 obj2 = self.pool.get(obj._columns[field2]._obj)
1909 if not obj2.search(cr, uid,
1910 [('id', '=', field_value[i][field2])]):
1912 # TODO add test for many2many and one2many
1913 field_value2[i][field2] = field_value[i][field2]
1914 field_value = field_value2
1915 value[field] = field_value
1917 # get the default values from the context
1918 for key in context or {}:
1919 if key.startswith('default_') and (key[8:] in fields_list):
1920 value[key[8:]] = context[key]
1923 def _where_calc(self, cr, user, args, active_test=True, context=None):
1928 # if the object has a field named 'active', filter out all inactive
1929 # records unless they were explicitely asked for
1930 if 'active' in self._columns and (active_test and context.get('active_test', True)):
1932 active_in_args = False
1934 if a[0] == 'active':
1935 active_in_args = True
1936 if not active_in_args:
1937 args.insert(0, ('active', '=', 1))
1939 args = [('active', '=', 1)]
1942 e = expression.expression(args)
1943 e.parse(cr, user, self, context)
1947 def search(self, cr, user, args, offset=0, limit=None, order=None,
1948 context=None, count=False):
1952 # implicit filter on current user
1955 args.insert(0, ('internal.create_uid', '=', user))
1957 result = self._where_calc(cr, user, args, context=context)
1959 return self.datas.keys()
1963 #Find the value of dict
1966 for id, data in self.datas.items():
1969 if limit and (counter > int(limit)):
1974 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
1975 elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
1976 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
1977 elif arg[1] in ['ilike']:
1978 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
1988 def unlink(self, cr, uid, ids, context=None):
1990 self._check_access(uid, id, 'unlink')
1991 self.datas.pop(id, None)
1993 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
1996 def perm_read(self, cr, user, ids, context=None, details=True):
1998 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
1999 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2001 self._check_access(user, id, 'read')
2003 'create_uid': credentials,
2004 'create_date': create_date,
2006 'write_date': False,
2011 def _check_removed_columns(self, cr, log=False):
2012 # nothing to check in memory...
2015 def exists(self, cr, uid, id, context=None):
2016 return id in self.datas
2018 class orm(orm_template):
2019 _sql_constraints = []
2021 _protected = ['read','write','create','default_get','perm_read','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data','search_count', 'exists']
2023 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2025 Get the list of records in list view grouped by the given ``groupby`` fields
2027 :param cr: database cursor
2028 :param uid: current user id
2029 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2030 :param fields: list of fields present in the list view specified on the object
2031 :param groupby: list of fields on which to groupby the records
2032 :type fields_list: list (example ['field_name_1', ...])
2033 :param offset: optional number of records to skip
2034 :param limit: optional max number of records to return
2035 :param context: context arguments, like lang, time zone
2036 :return: list of dictionaries(one dictionary for each record) containing:
2038 * the values of fields grouped by the fields in ``groupby`` argument
2039 * __domain: list of tuples specifying the search criteria
2040 * __context: dictionary with argument like ``groupby``
2041 :rtype: [{'field_name_1': value, ...]
2042 :raise AccessError: * if user has no read rights on the requested object
2043 * if user tries to bypass access rules for read on the requested object
2046 context = context or {}
2047 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2049 fields = self._columns.keys()
2051 (where_clause, where_params, tables) = self._where_calc(cr, uid, domain, context=context)
2052 dom = self.pool.get('ir.rule').domain_get(cr, uid, self._name, 'read', context=context)
2053 where_clause = where_clause + dom[0]
2054 where_params = where_params + dom[1]
2059 # Take care of adding join(s) if groupby is an '_inherits'ed field
2060 groupby_list = groupby
2062 if groupby and isinstance(groupby, list):
2063 groupby = groupby[0]
2064 tables, where_clause = self._inherits_join_calc(groupby,tables,where_clause)
2066 if len(where_clause):
2067 where_clause = ' where '+string.join(where_clause, ' and ')
2070 limit_str = limit and ' limit %d' % limit or ''
2071 offset_str = offset and ' offset %d' % offset or ''
2073 fget = self.fields_get(cr, uid, fields)
2074 float_int_fields = filter(lambda x: fget[x]['type'] in ('float','integer'), fields)
2080 if fget.get(groupby,False) and fget[groupby]['type'] in ('date','datetime'):
2081 flist = "to_char(%s,'yyyy-mm') as %s "%(groupby,groupby)
2082 groupby = "to_char(%s,'yyyy-mm')"%(groupby)
2087 fields_pre = [f for f in float_int_fields if
2088 f == self.CONCURRENCY_CHECK_FIELD
2089 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2090 for f in fields_pre:
2091 if f not in ['id','sequence']:
2092 operator = fget[f].get('group_operator','sum')
2095 flist += operator+'('+f+') as '+f
2098 gb = ' group by '+groupby
2101 cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_params)
2104 for r in cr.dictfetchall():
2105 for fld,val in r.items():
2106 if val == None:r[fld] = False
2107 alldata[r['id']] = r
2109 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2110 today = datetime.date.today()
2113 d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
2114 if not isinstance(groupby_list,(str, unicode)):
2115 if groupby or not context.get('group_by_no_leaf', False):
2116 d['__context'] = {'group_by':groupby_list[1:]}
2117 if groupby and fget.has_key(groupby):
2118 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
2119 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
2120 days = calendar.monthrange(dt.year, dt.month)[1]
2122 d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
2123 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),\
2124 (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
2125 elif fget[groupby]['type'] == 'many2one':
2126 d[groupby] = d[groupby] and ((type(d[groupby])==type(1)) and d[groupby] or d[groupby][1]) or ''
2128 del alldata[d['id']][groupby]
2129 d.update(alldata[d['id']])
2133 def _inherits_join_calc(self, field, tables, where_clause):
2135 Adds missing table select and join clause(s) for reaching
2136 the field coming from an '_inherits' parent table.
2138 :param tables: list of table._table names enclosed in double quotes as returned
2142 current_table = self
2143 while field in current_table._inherit_fields and not field in current_table._columns:
2144 parent_table = self.pool.get(current_table._inherit_fields[field][0])
2145 parent_table_name = parent_table._table
2146 if '"%s"'%parent_table_name not in tables:
2147 tables.append('"%s"'%parent_table_name)
2148 where_clause.append('(%s.%s = %s.id)' % (current_table._table, current_table._inherits[parent_table._name], parent_table_name))
2149 current_table = parent_table
2150 return (tables, where_clause)
2152 def _parent_store_compute(self, cr):
2153 if not self._parent_store:
2155 logger = netsvc.Logger()
2156 logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2157 def browse_rec(root, pos=0):
2159 where = self._parent_name+'='+str(root)
2161 where = self._parent_name+' IS NULL'
2162 if self._parent_order:
2163 where += ' order by '+self._parent_order
2164 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2166 childs = cr.fetchall()
2168 pos2 = browse_rec(id[0], pos2)
2169 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
2171 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2172 if self._parent_order:
2173 query += ' order by '+self._parent_order
2176 for (root,) in cr.fetchall():
2177 pos = browse_rec(root, pos)
2180 def _update_store(self, cr, f, k):
2181 logger = netsvc.Logger()
2182 logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2183 ss = self._columns[k]._symbol_set
2184 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2185 cr.execute('select id from '+self._table)
2186 ids_lst = map(lambda x: x[0], cr.fetchall())
2189 ids_lst = ids_lst[40:]
2190 res = f.get(cr, self, iids, k, 1, {})
2191 for key,val in res.items():
2194 # if val is a many2one, just write the ID
2195 if type(val)==tuple:
2197 if (val<>False) or (type(val)<>bool):
2198 cr.execute(update_query, (ss[1](val), key))
2200 def _check_removed_columns(self, cr, log=False):
2201 logger = netsvc.Logger()
2202 # iterate on the database columns to drop the NOT NULL constraints
2203 # of fields which were required but have been removed (or will be added by another module)
2204 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2205 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2206 cr.execute("SELECT a.attname, a.attnotnull"
2207 " FROM pg_class c, pg_attribute a"
2208 " WHERE c.relname=%s"
2209 " AND c.oid=a.attrelid"
2210 " AND a.attisdropped=%s"
2211 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2212 " AND a.attname NOT IN %s" ,(self._table, False, tuple(columns))),
2214 for column in cr.dictfetchall():
2216 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))
2217 if column['attnotnull']:
2218 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2220 def _auto_init(self, cr, context={}):
2221 store_compute = False
2222 logger = netsvc.Logger()
2225 self._field_create(cr, context=context)
2226 if getattr(self, '_auto', True):
2227 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s" ,( self._table,))
2229 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2230 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2233 if self._parent_store:
2234 cr.execute("""SELECT c.relname
2235 FROM pg_class c, pg_attribute a
2236 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2237 """, (self._table, 'parent_left'))
2239 if 'parent_left' not in self._columns:
2240 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2241 if 'parent_right' not in self._columns:
2242 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2243 if self._columns[self._parent_name].ondelete != 'cascade':
2244 logger.notifyChannel('orm', netsvc.LOG_ERROR, "The column %s on object %s must be set as ondelete='cascade'" % (self._parent_name, self._name))
2245 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2246 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2248 store_compute = True
2250 if self._log_access:
2252 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2253 'create_date': 'TIMESTAMP',
2254 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2255 'write_date': 'TIMESTAMP'
2260 FROM pg_class c, pg_attribute a
2261 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2262 """, (self._table, k))
2264 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2267 self._check_removed_columns(cr, log=False)
2269 # iterate on the "object columns"
2270 todo_update_store = []
2271 update_custom_fields = context.get('update_custom_fields', False)
2272 for k in self._columns:
2273 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2275 #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2276 #Not Updating Custom fields
2277 if k.startswith('x_') and not update_custom_fields:
2279 f = self._columns[k]
2281 if isinstance(f, fields.one2many):
2282 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2284 if self.pool.get(f._obj):
2285 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2286 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2287 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2290 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))
2291 res = cr.fetchone()[0]
2293 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2294 elif isinstance(f, fields.many2many):
2295 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2296 if not cr.dictfetchall():
2297 if not self.pool.get(f._obj):
2298 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2299 ref = self.pool.get(f._obj)._table
2300 # ref = f._obj.replace('.', '_')
2301 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))
2302 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2303 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2304 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2307 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 " \
2308 "FROM pg_class c,pg_attribute a,pg_type t " \
2309 "WHERE c.relname=%s " \
2310 "AND a.attname=%s " \
2311 "AND c.oid=a.attrelid " \
2312 "AND a.atttypid=t.oid", (self._table, k))
2313 res = cr.dictfetchall()
2314 if not res and hasattr(f,'oldname'):
2315 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 " \
2316 "FROM pg_class c,pg_attribute a,pg_type t " \
2317 "WHERE c.relname=%s " \
2318 "AND a.attname=%s " \
2319 "AND c.oid=a.attrelid " \
2320 "AND a.atttypid=t.oid", (self._table, f.oldname))
2321 res_old = cr.dictfetchall()
2322 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2323 if res_old and len(res_old)==1:
2324 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2326 res[0]['attname'] = k
2329 if not isinstance(f, fields.function) or f.store:
2331 # add the missing field
2332 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2333 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2336 if not create and k in self._defaults:
2337 if callable(self._defaults[k]):
2338 default = self._defaults[k](self, cr, 1, context)
2340 default = self._defaults[k]
2342 ss = self._columns[k]._symbol_set
2343 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2344 cr.execute(query, (ss[1](default),))
2346 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2348 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2350 if isinstance(f, fields.function):
2352 if f.store is not True:
2353 order = f.store[f.store.keys()[0]][2]
2354 todo_update_store.append((order, f,k))
2356 # and add constraints if needed
2357 if isinstance(f, fields.many2one):
2358 if not self.pool.get(f._obj):
2359 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2360 ref = self.pool.get(f._obj)._table
2361 # ref = f._obj.replace('.', '_')
2362 # ir_actions is inherited so foreign key doesn't work on it
2363 if ref != 'ir_actions':
2364 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2366 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2370 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2372 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))
2376 f_pg_type = f_pg_def['typname']
2377 f_pg_size = f_pg_def['size']
2378 f_pg_notnull = f_pg_def['attnotnull']
2379 if isinstance(f, fields.function) and not f.store and\
2380 not getattr(f, 'nodrop', False):
2381 logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2382 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2386 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2391 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2392 ('varchar', 'text', 'TEXT', ''),
2393 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2394 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2395 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2396 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2398 # !!! Avoid reduction of varchar field !!!
2399 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2400 # if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
2401 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2402 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2403 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2404 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2405 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2408 if (f_pg_type==c[0]) and (f._type==c[1]):
2409 if f_pg_type != f_obj_type:
2410 if f_pg_type != f_obj_type:
2411 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2413 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2414 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2415 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2416 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2420 if f_pg_type != f_obj_type:
2422 logger.notifyChannel('orm', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed type (DB = %s, def = %s) but unable to migrate this change !" % (k, self._table, f_pg_type, f._type))
2424 # if the field is required and hasn't got a NOT NULL constraint
2425 if f.required and f_pg_notnull == 0:
2426 # set the field to the default value if any
2427 if k in self._defaults:
2428 if callable(self._defaults[k]):
2429 default = self._defaults[k](self, cr, 1, context)
2431 default = self._defaults[k]
2433 if (default is not None):
2434 ss = self._columns[k]._symbol_set
2435 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2436 cr.execute(query, (ss[1](default),))
2437 # add the NOT NULL constraint
2440 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2443 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))
2445 elif not f.required and f_pg_notnull == 1:
2446 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2448 indexname = '%s_%s_index' % (self._table, k)
2449 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2450 res = cr.dictfetchall()
2451 if not res and f.select:
2452 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2454 if res and not f.select:
2455 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2457 if isinstance(f, fields.many2one):
2458 ref = self.pool.get(f._obj)._table
2459 if ref != 'ir_actions':
2460 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2461 'pg_attribute as att1, pg_attribute as att2 '
2462 'WHERE con.conrelid = cl1.oid '
2463 'AND cl1.relname = %s '
2464 'AND con.confrelid = cl2.oid '
2465 'AND cl2.relname = %s '
2466 'AND array_lower(con.conkey, 1) = 1 '
2467 'AND con.conkey[1] = att1.attnum '
2468 'AND att1.attrelid = cl1.oid '
2469 'AND att1.attname = %s '
2470 'AND array_lower(con.confkey, 1) = 1 '
2471 'AND con.confkey[1] = att2.attnum '
2472 'AND att2.attrelid = cl2.oid '
2473 'AND att2.attname = %s '
2474 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2475 res = cr.dictfetchall()
2477 if res[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2478 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
2479 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2482 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2483 for order,f,k in todo_update_store:
2484 todo_end.append((order, self._update_store, (f, k)))
2487 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2488 create = not bool(cr.fetchone())
2490 cr.commit() # start a new transaction
2492 for (key, con, _) in self._sql_constraints:
2493 conname = '%s_%s' % (self._table, key)
2494 cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2495 if not cr.dictfetchall():
2496 query = 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,)
2501 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))
2505 if hasattr(self, "_sql"):
2506 for line in self._sql.split(';'):
2507 line2 = line.replace('\n', '').strip()
2512 self._parent_store_compute(cr)
2516 def __init__(self, cr):
2517 super(orm, self).__init__(cr)
2519 if not hasattr(self, '_log_access'):
2520 # if not access is not specify, it is the same value as _auto
2521 self._log_access = getattr(self, "_auto", True)
2523 self._columns = self._columns.copy()
2524 for store_field in self._columns:
2525 f = self._columns[store_field]
2526 if hasattr(f, 'digits_change'):
2528 if not isinstance(f, fields.function):
2532 if self._columns[store_field].store is True:
2533 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2535 sm = self._columns[store_field].store
2536 for object, aa in sm.items():
2538 (fnct,fields2,order,length)=aa
2540 (fnct,fields2,order)=aa
2543 raise except_orm('Error',
2544 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2545 self.pool._store_function.setdefault(object, [])
2547 for x,y,z,e,f,l in self.pool._store_function[object]:
2548 if (x==self._name) and (y==store_field) and (e==fields2):
2552 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2553 self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2555 for (key, _, msg) in self._sql_constraints:
2556 self.pool._sql_error[self._table+'_'+key] = msg
2558 # Load manual fields
2560 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2562 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2563 for field in cr.dictfetchall():
2564 if field['name'] in self._columns:
2567 'string': field['field_description'],
2568 'required': bool(field['required']),
2569 'readonly': bool(field['readonly']),
2570 'domain': field['domain'] or None,
2571 'size': field['size'],
2572 'ondelete': field['on_delete'],
2573 'translate': (field['translate']),
2574 #'select': int(field['select_level'])
2577 if field['ttype'] == 'selection':
2578 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2579 elif field['ttype'] == 'reference':
2580 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2581 elif field['ttype'] == 'many2one':
2582 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2583 elif field['ttype'] == 'one2many':
2584 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2585 elif field['ttype'] == 'many2many':
2586 _rel1 = field['relation'].replace('.', '_')
2587 _rel2 = field['model'].replace('.', '_')
2588 _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2589 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2591 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2593 self._inherits_reload()
2594 if not self._sequence:
2595 self._sequence = self._table+'_id_seq'
2596 for k in self._defaults:
2597 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,)
2598 for f in self._columns:
2599 self._columns[f].restart()
2601 def default_get(self, cr, uid, fields_list, context=None):
2603 To Get default field values of given fields list of the model
2605 :param cr: database cursor
2606 :param uid: current user id
2607 :param fields_list: list of fields to get the default value
2608 :type fields_list: list (example ['field1', 'field2',])
2609 :param context: context arguments, like lang, time zone
2610 :return: dictionary of the default values for fields (set on the object class, by the user preferences, or via the context)
2616 # get the default values for the inherited fields
2617 for t in self._inherits.keys():
2618 value.update(self.pool.get(t).default_get(cr, uid, fields_list,
2621 # get the default values defined in the object
2622 for f in fields_list:
2623 if f in self._defaults:
2624 if callable(self._defaults[f]):
2625 value[f] = self._defaults[f](self, cr, uid, context)
2627 value[f] = self._defaults[f]
2629 fld_def = ((f in self._columns) and self._columns[f]) \
2630 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
2633 if isinstance(fld_def, fields.property):
2634 property_obj = self.pool.get('ir.property')
2635 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
2637 if isinstance(prop_value, (browse_record, browse_null)):
2638 value[f] = prop_value.id
2640 value[f] = prop_value
2645 # get the default values set by the user and override the default
2646 # values defined in the object
2647 ir_values_obj = self.pool.get('ir.values')
2648 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
2649 for id, field, field_value in res:
2650 if field in fields_list:
2651 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
2652 if fld_def._type in ('many2one', 'one2one'):
2653 obj = self.pool.get(fld_def._obj)
2654 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
2656 if fld_def._type in ('many2many'):
2657 obj = self.pool.get(fld_def._obj)
2659 for i in range(len(field_value)):
2660 if not obj.search(cr, uid, [('id', '=',
2663 field_value2.append(field_value[i])
2664 field_value = field_value2
2665 if fld_def._type in ('one2many'):
2666 obj = self.pool.get(fld_def._obj)
2668 for i in range(len(field_value)):
2669 field_value2.append({})
2670 for field2 in field_value[i]:
2671 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
2672 obj2 = self.pool.get(obj._columns[field2]._obj)
2673 if not obj2.search(cr, uid,
2674 [('id', '=', field_value[i][field2])]):
2676 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
2677 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
2678 if not obj2.search(cr, uid,
2679 [('id', '=', field_value[i][field2])]):
2681 # TODO add test for many2many and one2many
2682 field_value2[i][field2] = field_value[i][field2]
2683 field_value = field_value2
2684 value[field] = field_value
2685 for key in context or {}:
2686 if key.startswith('default_') and (key[8:] in fields_list):
2687 value[key[8:]] = context[key]
2691 # Update objects that uses this one to update their _inherits fields
2694 def _inherits_reload_src(self):
2695 for obj in self.pool.obj_pool.values():
2696 if self._name in obj._inherits:
2697 obj._inherits_reload()
2699 def _inherits_reload(self):
2701 for table in self._inherits:
2702 res.update(self.pool.get(table)._inherit_fields)
2703 for col in self.pool.get(table)._columns.keys():
2704 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2705 for col in self.pool.get(table)._inherit_fields.keys():
2706 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2707 self._inherit_fields = res
2708 self._inherits_reload_src()
2710 def __getattr__(self, name):
2712 Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2713 (though inherits doesn't use Python inheritance).
2714 Handles translating between local ids and remote ids.
2715 Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2716 when you have inherits.
2718 for model, field in self._inherits.iteritems():
2719 proxy = self.pool.get(model)
2720 if hasattr(proxy, name):
2721 attribute = getattr(proxy, name)
2722 if not hasattr(attribute, '__call__'):
2726 return super(orm, self).__getattr__(name)
2728 def _proxy(cr, uid, ids, *args, **kwargs):
2729 objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2730 lst = [obj[field].id for obj in objects if obj[field]]
2731 return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2736 def fields_get(self, cr, user, fields=None, context=None):
2738 Get the description of list of fields
2740 :param cr: database cursor
2741 :param user: current user id
2742 :param fields: list of fields
2743 :param context: context arguments, like lang, time zone
2744 :return: dictionary of field dictionaries, each one describing a field of the business object
2745 :raise AccessError: * if user has no create/write rights on the requested object
2748 ira = self.pool.get('ir.model.access')
2749 read_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2750 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2751 return super(orm, self).fields_get(cr, user, fields, context, read_access)
2753 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2755 Read records with given ids with the given fields
2757 :param cr: database cursor
2758 :param user: current user id
2759 :param ids: id or list of the ids of the records to read
2760 :param fields: optional list of field names to return (default: all fields would be returned)
2761 :type fields: list (example ['field_name_1', ...])
2762 :param context(optional, highly recommended): context arguments, like lang, time zone
2763 :return: list of dictionaries((dictionary per record asked)) with requested field values
2764 :rtype: [{‘name_of_the_field’: value, ...}, ...]
2765 :raise AccessError: * if user has no read rights on the requested object
2766 * if user tries to bypass access rules for read on the requested object
2771 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2773 fields = self._columns.keys() + self._inherit_fields.keys()
2774 if isinstance(ids, (int, long)):
2778 select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2779 result = self._read_flat(cr, user, select, fields, context, load)
2782 for key, v in r.items():
2785 if key in self._columns:
2786 column = self._columns[key]
2787 elif key in self._inherit_fields:
2788 column = self._inherit_fields[key][2]
2791 if v and column._type == 'reference':
2792 model_name, ref_id = v.split(',', 1)
2793 model = self.pool.get(model_name)
2797 cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
2798 reset = not cr.fetchone()[0]
2800 if column._classic_write:
2801 query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
2802 cr.execute(query, (r['id'],))
2805 if isinstance(ids, (int, long, dict)):
2806 return result and result[0] or False
2809 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2812 #ids = map(lambda x:int(x), ids)
2815 if fields_to_read == None:
2816 fields_to_read = self._columns.keys()
2818 # construct a clause for the rules :
2819 d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2820 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2821 fields_pre = [f for f in fields_to_read if
2822 f == self.CONCURRENCY_CHECK_FIELD
2823 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2824 ] + self._inherits.values()
2828 def convert_field(f):
2829 if f in ('create_date', 'write_date'):
2830 return "date_trunc('second', %s) as %s" % (f, f)
2831 if f == self.CONCURRENCY_CHECK_FIELD:
2832 if self._log_access:
2833 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2834 return "now()::timestamp AS %s" % (f,)
2835 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2836 return 'length("%s") as "%s"' % (f, f)
2837 return '"%s"' % (f,)
2838 fields_pre2 = map(convert_field, fields_pre)
2839 order_by = self._parent_order or self._order
2840 select_fields = ','.join(fields_pre2 + ['id'])
2841 query = 'SELECT %s FROM "%s" WHERE id IN %%s' % (select_fields, self._table)
2843 query += " AND " + d1
2844 query += " ORDER BY " + order_by
2845 for sub_ids in cr.split_for_in_conditions(ids):
2847 cr.execute(query, [tuple(sub_ids)] + d2)
2848 if cr.rowcount != len(sub_ids):
2849 raise except_orm(_('AccessError'),
2850 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2852 cr.execute(query, (tuple(sub_ids),))
2853 res.extend(cr.dictfetchall())
2855 res = map(lambda x: {'id': x}, ids)
2857 for f in fields_pre:
2858 if f == self.CONCURRENCY_CHECK_FIELD:
2860 if self._columns[f].translate:
2861 ids = map(lambda x: x['id'], res)
2862 #TODO: optimize out of this loop
2863 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2865 r[f] = res_trans.get(r['id'], False) or r[f]
2867 for table in self._inherits:
2868 col = self._inherits[table]
2869 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
2872 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
2880 if not record[col]:# if the record is deleted from _inherits table?
2882 record.update(res3[record[col]])
2883 if col not in fields_to_read:
2886 # all fields which need to be post-processed by a simple function (symbol_get)
2887 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
2890 for f in fields_post:
2891 r[f] = self._columns[f]._symbol_get(r[f])
2892 ids = map(lambda x: x['id'], res)
2894 # all non inherited fields for which the attribute whose name is in load is False
2895 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2897 # Compute POST fields
2899 for f in fields_post:
2900 todo.setdefault(self._columns[f]._multi, [])
2901 todo[self._columns[f]._multi].append(f)
2902 for key,val in todo.items():
2904 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2907 if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
2908 record[pos] = res2[record['id']][pos]
2911 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2914 record[f] = res2[record['id']]
2918 #for f in fields_post:
2919 # # get the value of that field for all records/ids
2920 # res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2921 # for record in res:
2922 # record[f] = res2[record['id']]
2926 for field in vals.copy():
2928 if field in self._columns:
2929 fobj = self._columns[field]
2936 for group in groups:
2937 module = group.split(".")[0]
2938 grp = group.split(".")[1]
2939 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" \
2940 (grp, module, 'res.groups', user))
2941 readonly = cr.fetchall()
2942 if readonly[0][0] >= 1:
2945 elif readonly[0][0] == 0:
2951 if type(vals[field]) == type([]):
2953 elif type(vals[field]) == type(0.0):
2955 elif type(vals[field]) == type(''):
2956 vals[field] = '=No Permission='
2961 def perm_read(self, cr, user, ids, context=None, details=True):
2963 Read the permission for record of the given ids
2965 :param cr: database cursor
2966 :param user: current user id
2967 :param ids: id or list of ids
2968 :param context: context arguments, like lang, time zone
2969 :param details: if True, \*_uid fields are replaced with the name of the user
2970 :return: list of ownership dictionaries for each requested record
2971 :rtype: list of dictionaries with the following keys:
2974 * create_uid: user who created the record
2975 * create_date: date when the record was created
2976 * write_uid: last user who changed the record
2977 * write_date: date of the last change to the record
2985 uniq = isinstance(ids, (int, long))
2989 if self._log_access:
2990 fields += ', create_uid, create_date, write_uid, write_date'
2991 query = 'SELECT %s FROM "%s" WHERE id IN %%s' % (fields, self._table)
2992 cr.execute(query, (tuple(ids),))
2993 res = cr.dictfetchall()
2996 r[key] = r[key] or False
2997 if key in ('write_uid', 'create_uid', 'uid') and details:
2999 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3004 def _check_concurrency(self, cr, ids, context):
3007 if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
3009 return "%s,%s" % (self._name, oid)
3010 santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3011 for i in range(0, len(ids), cr.IN_MAX):
3012 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
3013 for oid in ids[i:i+cr.IN_MAX]
3014 if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
3016 cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
3019 raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
3021 def check_access_rule(self, cr, uid, ids, operation, context=None):
3022 """Verifies that the operation given by ``operation`` is allowed for the user
3023 according to ir.rules.
3025 :param operation: one of ``write``, ``unlink``
3026 :raise except_orm: * if current ir.rules do not permit this operation.
3027 :return: None if the operation is allowed
3029 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3031 where_clause = ' and ' + ' and '.join(where_clause)
3032 for sub_ids in cr.split_for_in_conditions(ids):
3033 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3034 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3035 [sub_ids] + where_params)
3036 if cr.rowcount != len(sub_ids):
3037 raise except_orm(_('AccessError'),
3038 _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
3039 % (operation, self._name))
3041 def unlink(self, cr, uid, ids, context=None):
3043 Delete records with given ids
3045 :param cr: database cursor
3046 :param uid: current user id
3047 :param ids: id or list of ids
3048 :param context(optional, highly recommended): context arguments, like lang, time zone
3050 :raise AccessError: * if user has no unlink rights on the requested object
3051 * if user tries to bypass access rules for unlink on the requested object
3052 :raise UserError: if the record is default property for other records
3057 if isinstance(ids, (int, long)):
3060 result_store = self._store_get_values(cr, uid, ids, None, context)
3062 self._check_concurrency(cr, ids, context)
3064 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3066 properties = self.pool.get('ir.property')
3067 domain = [('res_id', '=', False),
3068 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3070 if properties.search(cr, uid, domain, context=context):
3071 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3073 wf_service = netsvc.LocalService("workflow")
3075 wf_service.trg_delete(uid, self._name, oid, cr)
3078 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3079 for sub_ids in cr.split_for_in_conditions(ids):
3080 cr.execute('delete from ' + self._table + ' ' \
3081 'where id IN %s', (sub_ids,))
3082 for order, object, store_ids, fields in result_store:
3083 if object != self._name:
3084 obj = self.pool.get(object)
3085 cr.execute('select id from '+obj._table+' where id IN %s',(tuple(store_ids),))
3086 rids = map(lambda x: x[0], cr.fetchall())
3088 obj._store_set_values(cr, uid, rids, fields, context)
3094 def write(self, cr, user, ids, vals, context=None):
3096 Update records with given ids with the given field values
3098 :param cr: database cursor
3099 :param user: current user id
3100 :type user: integer (example 1)
3101 :param ids: id or list of ids
3102 :param vals: dictionary of field values to update
3103 :type vals: dictionary (example {'field_name': 'value', ...})
3104 :param context(optional, highly recommended): context arguments, like lang, time zone
3106 :raise AccessError: * if user has no write rights on the requested object
3107 * if user tries to bypass access rules for write on the requested object
3108 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3109 :raise UserError: if recurssion is found
3111 vals format for relational field type.
3115 For write operation on a many2many fields a list of tuple is
3116 expected. The folowing tuples are accepted:
3117 (0, 0, { fields }) create
3118 (1, ID, { fields }) update (write fields to ID)
3119 (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
3120 (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
3121 (4, ID) link (add a relationship)
3123 (6, 0, list of ids) set a list of links
3127 [(6, 0, [8, 5, 6, 4])] set the many2many to ids [8, 5, 6, 4]
3129 + one2many field : [(0, 0, dictionary of values)] (example: [(0, 0, {'field_name':field_value, ...})])
3130 + many2one field : ID of related record
3131 + reference field : model name, id (example: 'product.product, 5')
3136 for field in vals.copy():
3138 if field in self._columns:
3139 fobj = self._columns[field]
3141 fobj = self._inherit_fields[field][2]
3148 for group in groups:
3149 module = group.split(".")[0]
3150 grp = group.split(".")[1]
3151 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" \
3152 (grp, module, 'res.groups', user))
3153 readonly = cr.fetchall()
3154 if readonly[0][0] >= 1:
3157 elif readonly[0][0] == 0:
3169 if isinstance(ids, (int, long)):
3172 self._check_concurrency(cr, ids, context)
3173 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3175 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3177 # No direct update of parent_left/right
3178 vals.pop('parent_left', None)
3179 vals.pop('parent_right', None)
3181 parents_changed = []
3182 if self._parent_store and (self._parent_name in vals):
3183 # The parent_left/right computation may take up to
3184 # 5 seconds. No need to recompute the values if the
3185 # parent is the same. Get the current value of the parent
3186 parent_val = vals[self._parent_name]
3188 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3189 (self._table, self._parent_name, self._parent_name)
3190 cr.execute(query, (tuple(ids), parent_val))
3192 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3193 (self._table, self._parent_name)
3194 cr.execute(query, (tuple(ids),))
3195 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3202 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3204 if field in self._columns:
3205 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3206 if (not totranslate) or not self._columns[field].translate:
3207 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3208 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3209 direct.append(field)
3211 upd_todo.append(field)
3213 updend.append(field)
3214 if field in self._columns \
3215 and hasattr(self._columns[field], 'selection') \
3217 if self._columns[field]._type == 'reference':
3218 val = vals[field].split(',')[0]
3221 if isinstance(self._columns[field].selection, (tuple, list)):
3222 if val not in dict(self._columns[field].selection):
3223 raise except_orm(_('ValidateError'),
3224 _('The value "%s" for the field "%s" is not in the selection') \
3225 % (vals[field], field))
3227 if val not in dict(self._columns[field].selection(
3228 self, cr, user, context=context)):
3229 raise except_orm(_('ValidateError'),
3230 _('The value "%s" for the field "%s" is not in the selection') \
3231 % (vals[field], field))
3233 if self._log_access:
3234 upd0.append('write_uid=%s')
3235 upd0.append('write_date=now()')
3239 self.check_access_rule(cr, user, ids, 'write', context=context)
3240 for sub_ids in cr.split_for_in_conditions(ids):
3241 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3242 'where id IN %s', upd1 + [sub_ids])
3247 if self._columns[f].translate:
3248 src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
3251 # Inserting value to DB
3252 self.write(cr, user, ids, {f:vals[f]})
3253 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3256 # call the 'set' method of fields which are not classic_write
3257 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3259 # default element in context must be removed when call a one2many or many2many
3260 rel_context = context.copy()
3261 for c in context.items():
3262 if c[0].startswith('default_'):
3263 del rel_context[c[0]]
3265 for field in upd_todo:
3267 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3269 for table in self._inherits:
3270 col = self._inherits[table]
3272 for sub_ids in cr.split_for_in_conditions(ids):
3273 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3274 'where id IN %s', (sub_ids,))
3275 nids.extend([x[0] for x in cr.fetchall()])
3279 if self._inherit_fields[val][0] == table:
3281 self.pool.get(table).write(cr, user, nids, v, context)
3283 self._validate(cr, user, ids, context)
3285 # TODO: use _order to set dest at the right position and not first node of parent
3286 # We can't defer parent_store computation because the stored function
3287 # fields that are computer may refer (directly or indirectly) to
3288 # parent_left/right (via a child_of domain)
3291 self.pool._init_parent[self._name]=True
3293 order = self._parent_order or self._order
3294 parent_val = vals[self._parent_name]
3296 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3298 clause, params = '%s IS NULL' % (self._parent_name,), ()
3299 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3300 parents = cr.fetchall()
3302 for id in parents_changed:
3303 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3304 pleft, pright = cr.fetchone()
3305 distance = pright - pleft + 1
3307 # Find Position of the element
3309 for (parent_pright, parent_id) in parents:
3312 position = parent_pright+1
3314 # It's the first node of the parent
3319 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3320 position = cr.fetchone()[0]+1
3322 if pleft < position <= pright:
3323 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3325 if pleft < position:
3326 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3327 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3328 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))
3330 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3331 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3332 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))
3334 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3338 for order, object, ids, fields in result:
3339 key = (object,tuple(fields))
3340 done.setdefault(key, {})
3341 # avoid to do several times the same computation
3344 if id not in done[key]:
3345 done[key][id] = True
3347 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3349 wf_service = netsvc.LocalService("workflow")
3351 wf_service.trg_write(user, self._name, id, cr)
3355 # TODO: Should set perm to user.xxx
3357 def create(self, cr, user, vals, context=None):
3359 Create new record with specified value
3361 :param cr: database cursor
3362 :param user: current user id
3363 :type user: integer (example 1)
3364 :param vals: dictionary for new record {'field_name': field_value, ...}
3365 :type vals: dictionary (example {'field_name': field_value, ...})
3366 :param context(optional, highly recommended): context arguments, like lang, time zone
3367 :type context: dictionary (example {'lang': 'en_us', ...})
3368 :return: id of new record created
3369 :raise AccessError: * if user has no create rights on the requested object
3370 * if user tries to bypass access rules for create on the requested object
3371 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3373 vals format for relational field type.
3375 + many2many field : [(6, 0, list of ids)] (example: [(6, 0, [8, 5, 6, 4])])
3376 + one2many field : [(0, 0, dictionary of values)] (example: [(0, 0, {'field_name':field_value, ...})])
3377 + many2one field : ID of related record
3378 + reference field : model name, id (example: 'product.product, 5')
3383 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3388 for (t, c) in self._inherits.items():
3390 avoid_table.append(t)
3391 for f in self._columns.keys(): # + self._inherit_fields.keys():
3395 for f in self._inherit_fields.keys():
3396 if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
3400 default_values = self.default_get(cr, user, default, context)
3401 for dv in default_values:
3402 if dv in self._columns and self._columns[dv]._type == 'many2many':
3403 if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
3404 default_values[dv] = [(6, 0, default_values[dv])]
3406 vals.update(default_values)
3409 for v in self._inherits:
3410 if self._inherits[v] not in vals:
3413 tocreate[v] = {'id' : vals[self._inherits[v]]}
3414 (upd0, upd1, upd2) = ('', '', [])
3416 for v in vals.keys():
3417 if v in self._inherit_fields:
3418 (table, col, col_detail) = self._inherit_fields[v]
3419 tocreate[table][v] = vals[v]
3422 if (v not in self._inherit_fields) and (v not in self._columns):
3425 # Try-except added to filter the creation of those records whose filds are readonly.
3426 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3428 cr.execute("SELECT nextval('"+self._sequence+"')")
3430 raise except_orm(_('UserError'),
3431 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3433 id_new = cr.fetchone()[0]
3434 for table in tocreate:
3435 if self._inherits[table] in vals:
3436 del vals[self._inherits[table]]
3438 record_id = tocreate[table].pop('id', None)
3440 if record_id is None or not record_id:
3441 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3443 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3445 upd0 += ','+self._inherits[table]
3447 upd2.append(record_id)
3449 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3450 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3452 for bool_field in bool_fields:
3453 if bool_field not in vals:
3454 vals[bool_field] = False
3456 for field in vals.copy():
3458 if field in self._columns:
3459 fobj = self._columns[field]
3461 fobj = self._inherit_fields[field][2]
3467 for group in groups:
3468 module = group.split(".")[0]
3469 grp = group.split(".")[1]
3470 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" % \
3471 (grp, module, 'res.groups', user))
3472 readonly = cr.fetchall()
3473 if readonly[0][0] >= 1:
3476 elif readonly[0][0] == 0:
3484 if self._columns[field]._classic_write:
3485 upd0 = upd0 + ',"' + field + '"'
3486 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3487 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3489 if not isinstance(self._columns[field], fields.related):
3490 upd_todo.append(field)
3491 if field in self._columns \
3492 and hasattr(self._columns[field], 'selection') \
3494 if self._columns[field]._type == 'reference':
3495 val = vals[field].split(',')[0]
3498 if isinstance(self._columns[field].selection, (tuple, list)):
3499 if val not in dict(self._columns[field].selection):
3500 raise except_orm(_('ValidateError'),
3501 _('The value "%s" for the field "%s" is not in the selection') \
3502 % (vals[field], field))
3504 if val not in dict(self._columns[field].selection(
3505 self, cr, user, context=context)):
3506 raise except_orm(_('ValidateError'),
3507 _('The value "%s" for the field "%s" is not in the selection') \
3508 % (vals[field], field))
3509 if self._log_access:
3510 upd0 += ',create_uid,create_date'
3513 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3514 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3515 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3517 if self._parent_store and not context.get('defer_parent_store_computation'):
3519 self.pool._init_parent[self._name]=True
3521 parent = vals.get(self._parent_name, False)
3523 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3525 result_p = cr.fetchall()
3526 for (pleft,) in result_p:
3531 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3532 pleft_old = cr.fetchone()[0]
3535 cr.execute('select max(parent_right) from '+self._table)
3536 pleft = cr.fetchone()[0] or 0
3537 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3538 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3539 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3541 # default element in context must be remove when call a one2many or many2many
3542 rel_context = context.copy()
3543 for c in context.items():
3544 if c[0].startswith('default_'):
3545 del rel_context[c[0]]
3548 for field in upd_todo:
3549 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3550 self._validate(cr, user, [id_new], context)
3552 if not context.get('no_store_function', False):
3553 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3556 for order, object, ids, fields2 in result:
3557 if not (object, ids, fields2) in done:
3558 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3559 done.append((object, ids, fields2))
3561 if self._log_create and not (context and context.get('no_store_function', False)):
3562 message = self._description + \
3564 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3566 self.log(cr, user, id_new, message, True, context=context)
3567 wf_service = netsvc.LocalService("workflow")
3568 wf_service.trg_create(user, self._name, id_new, cr)
3571 def _store_get_values(self, cr, uid, ids, fields, context):
3573 fncts = self.pool._store_function.get(self._name, [])
3574 for fnct in range(len(fncts)):
3579 for f in (fields or []):
3580 if f in fncts[fnct][3]:
3586 result.setdefault(fncts[fnct][0], {})
3588 # uid == 1 for accessing objects having rules defined on store fields
3589 ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3590 for id in filter(None, ids2):
3591 result[fncts[fnct][0]].setdefault(id, [])
3592 result[fncts[fnct][0]][id].append(fnct)
3594 for object in result:
3596 for id,fnct in result[object].items():
3597 k2.setdefault(tuple(fnct), [])
3598 k2[tuple(fnct)].append(id)
3599 for fnct,id in k2.items():
3600 dict.setdefault(fncts[fnct[0]][4],[])
3601 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3609 def _store_set_values(self, cr, uid, ids, fields, context):
3614 if self._log_access:
3615 cr.execute('select id,write_date from '+self._table+' where id IN %s',(tuple(ids),))
3619 field_dict.setdefault(r[0], [])
3620 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3621 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3622 for i in self.pool._store_function.get(self._name, []):
3624 up_write_date = write_date + datetime.timedelta(hours=i[5])
3625 if datetime.datetime.now() < up_write_date:
3627 field_dict[r[0]].append(i[1])
3633 if self._columns[f]._multi not in keys:
3634 keys.append(self._columns[f]._multi)
3635 todo.setdefault(self._columns[f]._multi, [])
3636 todo[self._columns[f]._multi].append(f)
3640 # uid == 1 for accessing objects having rules defined on store fields
3641 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3642 for id,value in result.items():
3644 for f in value.keys():
3645 if f in field_dict[id]:
3652 if self._columns[v]._type in ('many2one', 'one2one'):
3654 value[v] = value[v][0]
3657 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3658 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3661 cr.execute('update "' + self._table + '" set ' + \
3662 string.join(upd0, ',') + ' where id = %s', upd1)
3666 # uid == 1 for accessing objects having rules defined on store fields
3667 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3668 for r in result.keys():
3670 if r in field_dict.keys():
3671 if f in field_dict[r]:
3673 for id,value in result.items():
3674 if self._columns[f]._type in ('many2one', 'one2one'):
3679 cr.execute('update "' + self._table + '" set ' + \
3680 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3686 def perm_write(self, cr, user, ids, fields, context=None):
3687 raise NotImplementedError(_('This method does not exist anymore'))
3689 # TODO: ameliorer avec NULL
3690 def _where_calc(self, cr, user, args, active_test=True, context=None):
3694 # if the object has a field named 'active', filter out all inactive
3695 # records unless they were explicitely asked for
3696 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3698 active_in_args = False
3700 if a[0] == 'active':
3701 active_in_args = True
3702 if not active_in_args:
3703 args.insert(0, ('active', '=', 1))
3705 args = [('active', '=', 1)]
3709 e = expression.expression(args)
3710 e.parse(cr, user, self, context)
3711 tables = e.get_tables()
3712 qu1, qu2 = e.to_sql()
3713 qu1 = qu1 and [qu1] or []
3715 qu1, qu2, tables = [], [], ['"%s"' % self._table]
3717 return (qu1, qu2, tables)
3719 def _check_qorder(self, word):
3720 if not regex_order.match(word):
3721 raise except_orm(_('AccessError'), _('Bad query.'))
3724 def search(self, cr, user, args, offset=0, limit=None, order=None,
3725 context=None, count=False):
3727 Search for record/s with or without domain
3729 :param cr: database cursor
3730 :param user: current user id
3731 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
3732 :param offset: optional number from search starts
3733 :param limit: optional max number of records to return
3734 :param order: optional columns to sort by (default: self._order=id )
3735 :param context(optional, highly recommended): context arguments, like lang, time zone
3736 :param count: if True, returns only the number of records matching the criteria, not their ids
3737 :return: id or list of ids of records matching the criteria
3738 :rtype: integer or list of integers
3739 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
3742 * =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right
3744 * '&' (default), '|', '!'
3749 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3750 # compute the where, order by, limit and offset clauses
3751 (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
3752 dom = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3760 qu1 = ' where '+string.join(qu1, ' and ')
3765 order_by = self._order
3767 self._check_qorder(order)
3768 o = order.split(' ')[0]
3769 if (o in self._columns) and getattr(self._columns[o], '_classic_write'):
3772 limit_str = limit and ' limit %d' % limit or ''
3773 offset_str = offset and ' offset %d' % offset or ''
3777 cr.execute('select count(%s.id) from ' % self._table +
3778 ','.join(tables) +qu1 + limit_str + offset_str, qu2)
3781 cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
3783 return [x[0] for x in res]
3785 # returns the different values ever entered for one field
3786 # this is used, for example, in the client when the user hits enter on
3788 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3791 if field in self._inherit_fields:
3792 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3794 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3796 def name_get(self, cr, user, ids, context=None):
3799 :param cr: database cursor
3800 :param user: current user id
3801 :type user: integer (example 1)
3802 :param ids: list of ids
3803 :param context: context arguments, like lang, time zone
3804 :return: tuples with the text representation of requested objects for to-many relationships
3811 if isinstance(ids, (int, long)):
3813 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
3814 [self._rec_name], context, load='_classic_write')]
3816 # private implementation of name_search, allows passing a dedicated user for the name_get part to
3817 # solve some access rights issues
3818 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
3825 args += [(self._rec_name, operator, name)]
3826 ids = self.search(cr, user, args, limit=limit, context=context)
3827 res = self.name_get(cr, name_get_uid or user, ids, context)
3830 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
3833 :param cr: database cursor
3834 :param user: current user id
3835 :param name: object name to search
3836 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
3837 :param operator: operator for search criterion
3838 :param context: context arguments, like lang, time zone
3839 :param limit: optional max number of records to return
3840 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
3842 This method is equivalent of search() on name + name_get()
3845 return self._name_search(cr, user, name, args, operator, context, limit)
3847 def copy_data(self, cr, uid, id, default=None, context=None):
3849 Copy given record's data with all its fields values
3851 :param cr: database cursor
3852 :param user: current user id
3853 :param ids: id of the record to copy
3854 :param default: dictionary of field values to update before saving the duplicate object
3855 :param context: context arguments, like lang, time zone
3856 :return: dictionary containing all the field values
3863 if 'state' not in default:
3864 if 'state' in self._defaults:
3865 if callable(self._defaults['state']):
3866 default['state'] = self._defaults['state'](self, cr, uid, context)
3868 default['state'] = self._defaults['state']
3870 context_wo_lang = context
3871 if 'lang' in context:
3872 del context_wo_lang['lang']
3873 data = self.read(cr, uid, [id], context=context_wo_lang)[0]
3875 fields = self.fields_get(cr, uid, context=context)
3877 ftype = fields[f]['type']
3879 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3883 data[f] = default[f]
3884 elif ftype == 'function':
3886 elif ftype == 'many2one':
3888 data[f] = data[f] and data[f][0]
3891 elif ftype in ('one2many', 'one2one'):
3893 rel = self.pool.get(fields[f]['relation'])
3895 # duplicate following the order of the ids
3896 # because we'll rely on it later for copying
3897 # translations in copy_translation()!
3899 for rel_id in data[f]:
3900 # the lines are first duplicated using the wrong (old)
3901 # parent but then are reassigned to the correct one thanks
3902 # to the (0, 0, ...)
3903 d = rel.copy_data(cr, uid, rel_id, context=context)
3904 res.append((0, 0, d))
3906 elif ftype == 'many2many':
3907 data[f] = [(6, 0, data[f])]
3911 # make sure we don't break the current parent_store structure and
3912 # force a clean recompute!
3913 for parent_column in ['parent_left', 'parent_right']:
3914 data.pop(parent_column, None)
3916 for v in self._inherits:
3917 del data[self._inherits[v]]
3920 def copy_translations(self, cr, uid, old_id, new_id, context=None):
3921 trans_obj = self.pool.get('ir.translation')
3922 fields = self.fields_get(cr, uid, context=context)
3924 translation_records = []
3925 for field_name, field_def in fields.items():
3926 # we must recursively copy the translations for o2o and o2m
3927 if field_def['type'] in ('one2one', 'one2many'):
3928 target_obj = self.pool.get(field_def['relation'])
3929 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
3930 # here we rely on the order of the ids to match the translations
3931 # as foreseen in copy_data()
3932 old_childs = sorted(old_record[field_name])
3933 new_childs = sorted(new_record[field_name])
3934 for (old_child, new_child) in zip(old_childs, new_childs):
3935 # recursive copy of translations here
3936 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
3937 # and for translatable fields we keep them for copy
3938 elif field_def.get('translate'):
3940 if field_name in self._columns:
3941 trans_name = self._name + "," + field_name
3942 elif field_name in self._inherit_fields:
3943 trans_name = self._inherit_fields[field_name][0] + "," + field_name
3945 trans_ids = trans_obj.search(cr, uid, [
3946 ('name', '=', trans_name),
3947 ('res_id','=', old_id)
3949 translation_records.extend(trans_obj.read(cr,uid,trans_ids,context=context))
3951 for record in translation_records:
3953 record['res_id'] = new_id
3954 trans_obj.create(cr, uid, record, context=context)
3957 def copy(self, cr, uid, id, default=None, context=None):
3959 Duplicate record with given id updating it with default values
3961 :param cr: database cursor
3962 :param uid: current user id
3963 :param id: id of the record to copy
3964 :param default: dictionary of field values to update before saving the duplicate object
3965 :type default: dictionary (example {'field_name': field_value, ...})
3966 :param context: context arguments, like lang, time zone
3970 data = self.copy_data(cr, uid, id, default, context)
3971 new_id = self.create(cr, uid, data, context)
3972 self.copy_translations(cr, uid, id, new_id, context)
3975 def exists(self, cr, uid, ids, context=None):
3976 if type(ids) in (int,long):
3978 query = 'SELECT count(1) FROM "%s"' % (self._table)
3979 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
3980 return cr.fetchone()[0] == len(ids)
3982 def check_recursion(self, cr, uid, ids, parent=None):
3984 Check recursion in records
3986 :param cr: database cursor
3987 :param uid: current user id
3988 :param ids: list of ids of records
3989 :param parent: parent field name
3990 :return: True or False based on recursion detection
3994 parent = self._parent_name
3996 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
3999 for i in range(0, len(ids), cr.IN_MAX):
4000 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4001 cr.execute(query, (tuple(sub_ids_parent),))
4002 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4003 ids_parent = ids_parent2
4004 for i in ids_parent:
4009 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4010 """Find out the XML ID of any database record, if there
4011 is one. This method works as a possible implementation
4012 for a function field, to be able to add it to any
4013 model object easily, referencing it as 'osv.osv.get_xml_id'.
4015 get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }
4017 :return: the fully qualified XML ID of the given object,
4018 defaulting to an empty string when there's none.
4020 result = dict.fromkeys(ids, '')
4021 model_data_obj = self.pool.get('ir.model.data')
4022 data_ids = model_data_obj.search(cr,uid,
4023 [('model','=',self._name),('res_id','in',ids)])
4024 data_results = model_data_obj.read(cr,uid,data_ids,
4025 ['name','module','res_id'])
4026 for record in data_results:
4027 result[record['res_id']] = '%(module)s.%(name)s' % record
4030 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: