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 vaccum(self, cr, uid):
1759 if self.check_id % self._check_time:
1762 max = time.time() - self._max_hours * 60 * 60
1763 for id in self.datas:
1764 if self.datas[id]['internal.date_access'] < max:
1766 self.unlink(cr, uid, tounlink)
1767 if len(self.datas)>self._max_count:
1768 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1770 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1771 self.unlink(cr, uid, ids)
1774 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1777 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
1778 if not fields_to_read:
1779 fields_to_read = self._columns.keys()
1783 if isinstance(ids, (int, long)):
1787 for f in fields_to_read:
1788 if id in self.datas:
1789 r[f] = self.datas[id].get(f, False)
1790 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1793 if id in self.datas:
1794 self.datas[id]['internal.date_access'] = time.time()
1795 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1796 for f in fields_post:
1797 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1798 for record in result:
1799 record[f] = res2[record['id']]
1800 if isinstance(ids_orig, (int, long)):
1804 def write(self, cr, user, ids, vals, context=None):
1807 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
1811 if self._columns[field]._classic_write:
1812 vals2[field] = vals[field]
1814 upd_todo.append(field)
1816 self.datas[id_new].update(vals2)
1817 self.datas[id_new]['internal.date_access'] = time.time()
1818 for field in upd_todo:
1819 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1820 self._validate(cr, user, [id_new], context)
1821 wf_service = netsvc.LocalService("workflow")
1822 wf_service.trg_write(user, self._name, id_new, cr)
1825 def create(self, cr, user, vals, context=None):
1826 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
1827 self.vaccum(cr, user)
1829 id_new = self.next_id
1831 for f in self._columns.keys():
1835 vals.update(self.default_get(cr, user, default, context))
1839 if self._columns[field]._classic_write:
1840 vals2[field] = vals[field]
1842 upd_todo.append(field)
1843 self.datas[id_new] = vals2
1844 self.datas[id_new]['internal.date_access'] = time.time()
1846 for field in upd_todo:
1847 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1848 self._validate(cr, user, [id_new], context)
1849 if self._log_create and not (context and context.get('no_store_function', False)):
1850 message = self._description + \
1852 self.name_get(cr, user, [id_new], context=context)[0][1] + \
1854 self.log(cr, user, id_new, message, True, context=context)
1855 wf_service = netsvc.LocalService("workflow")
1856 wf_service.trg_create(user, self._name, id_new, cr)
1859 def default_get(self, cr, uid, fields_list, context=None):
1860 self.view_init(cr, uid, fields_list, context)
1864 # get the default values for the inherited fields
1865 for f in fields_list:
1866 if f in self._defaults:
1867 if callable(self._defaults[f]):
1868 value[f] = self._defaults[f](self, cr, uid, context)
1870 value[f] = self._defaults[f]
1872 fld_def = ((f in self._columns) and self._columns[f]) \
1873 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1876 # get the default values set by the user and override the default
1877 # values defined in the object
1878 ir_values_obj = self.pool.get('ir.values')
1879 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1880 for id, field, field_value in res:
1881 if field in fields_list:
1882 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1883 if fld_def._type in ('many2one', 'one2one'):
1884 obj = self.pool.get(fld_def._obj)
1885 if not obj.search(cr, uid, [('id', '=', field_value)]):
1887 if fld_def._type in ('many2many'):
1888 obj = self.pool.get(fld_def._obj)
1890 for i in range(len(field_value)):
1891 if not obj.search(cr, uid, [('id', '=',
1894 field_value2.append(field_value[i])
1895 field_value = field_value2
1896 if fld_def._type in ('one2many'):
1897 obj = self.pool.get(fld_def._obj)
1899 for i in range(len(field_value)):
1900 field_value2.append({})
1901 for field2 in field_value[i]:
1902 if obj._columns[field2]._type in ('many2one', 'one2one'):
1903 obj2 = self.pool.get(obj._columns[field2]._obj)
1904 if not obj2.search(cr, uid,
1905 [('id', '=', field_value[i][field2])]):
1907 # TODO add test for many2many and one2many
1908 field_value2[i][field2] = field_value[i][field2]
1909 field_value = field_value2
1910 value[field] = field_value
1912 # get the default values from the context
1913 for key in context or {}:
1914 if key.startswith('default_') and (key[8:] in fields_list):
1915 value[key[8:]] = context[key]
1918 def _where_calc(self, cr, user, args, active_test=True, context=None):
1923 # if the object has a field named 'active', filter out all inactive
1924 # records unless they were explicitely asked for
1925 if 'active' in self._columns and (active_test and context.get('active_test', True)):
1927 active_in_args = False
1929 if a[0] == 'active':
1930 active_in_args = True
1931 if not active_in_args:
1932 args.insert(0, ('active', '=', 1))
1934 args = [('active', '=', 1)]
1937 e = expression.expression(args)
1938 e.parse(cr, user, self, context)
1939 res=e.__dict__['_expression__exp']
1942 def search(self, cr, user, args, offset=0, limit=None, order=None,
1943 context=None, count=False):
1946 result = self._where_calc(cr, user, args, context=context)
1948 return self.datas.keys()
1952 #Find the value of dict
1955 for id, data in self.datas.items():
1958 if limit and (counter >int(limit)):
1963 val =eval('data[arg[0]]'+'==' +' arg[2]', locals())
1964 elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
1965 val =eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
1966 elif arg[1] in ['ilike']:
1967 if str(data[arg[0]]).find(str(arg[2]))!=-1:
1982 def unlink(self, cr, uid, ids, context=None):
1983 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
1985 if id in self.datas:
1988 cr.execute('delete from wkf_instance where res_type=%s and res_id in %s', (self._name, tuple(ids)))
1991 def perm_read(self, cr, user, ids, context=None, details=True):
1995 'create_uid': (user, 'Root'),
1996 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'),
1998 'write_date': False,
2003 def _check_removed_columns(self, cr, log=False):
2004 # nothing to check in memory...
2007 def exists(self, cr, uid, id, context=None):
2008 return id in self.datas
2010 class orm(orm_template):
2011 _sql_constraints = []
2013 _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']
2015 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2017 Get the list of records in list view grouped by the given ``groupby`` fields
2019 :param cr: database cursor
2020 :param uid: current user id
2021 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2022 :param fields: list of fields present in the list view specified on the object
2023 :param groupby: list of fields on which to groupby the records
2024 :type fields_list: list (example ['field_name_1', ...])
2025 :param offset: optional number of records to skip
2026 :param limit: optional max number of records to return
2027 :param context: context arguments, like lang, time zone
2028 :return: list of dictionaries(one dictionary for each record) containing:
2030 * the values of fields grouped by the fields in ``groupby`` argument
2031 * __domain: list of tuples specifying the search criteria
2032 * __context: dictionary with argument like ``groupby``
2033 :rtype: [{'field_name_1': value, ...]
2034 :raise AccessError: * if user has no read rights on the requested object
2035 * if user tries to bypass access rules for read on the requested object
2038 context = context or {}
2039 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2041 fields = self._columns.keys()
2043 (where_clause, where_params, tables) = self._where_calc(cr, uid, domain, context=context)
2044 dom = self.pool.get('ir.rule').domain_get(cr, uid, self._name, 'read', context=context)
2045 where_clause = where_clause + dom[0]
2046 where_params = where_params + dom[1]
2051 # Take care of adding join(s) if groupby is an '_inherits'ed field
2052 groupby_list = groupby
2054 if groupby and isinstance(groupby, list):
2055 groupby = groupby[0]
2056 tables, where_clause = self._inherits_join_calc(groupby,tables,where_clause)
2058 if len(where_clause):
2059 where_clause = ' where '+string.join(where_clause, ' and ')
2062 limit_str = limit and ' limit %d' % limit or ''
2063 offset_str = offset and ' offset %d' % offset or ''
2065 fget = self.fields_get(cr, uid, fields)
2066 float_int_fields = filter(lambda x: fget[x]['type'] in ('float','integer'), fields)
2072 if fget.get(groupby,False) and fget[groupby]['type'] in ('date','datetime'):
2073 flist = "to_char(%s,'yyyy-mm') as %s "%(groupby,groupby)
2074 groupby = "to_char(%s,'yyyy-mm')"%(groupby)
2079 fields_pre = [f for f in float_int_fields if
2080 f == self.CONCURRENCY_CHECK_FIELD
2081 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2082 for f in fields_pre:
2083 if f not in ['id','sequence']:
2084 operator = fget[f].get('group_operator','sum')
2087 flist += operator+'('+f+') as '+f
2090 gb = ' group by '+groupby
2093 cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_params)
2096 for r in cr.dictfetchall():
2097 for fld,val in r.items():
2098 if val == None:r[fld] = False
2099 alldata[r['id']] = r
2101 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2102 today = datetime.date.today()
2105 d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain
2106 if not isinstance(groupby_list,(str, unicode)):
2107 if groupby or not context.get('group_by_no_leaf', False):
2108 d['__context'] = {'group_by':groupby_list[1:]}
2109 if groupby and fget.has_key(groupby):
2110 if d[groupby] and fget[groupby]['type'] in ('date','datetime'):
2111 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m')
2112 days = calendar.monthrange(dt.year, dt.month)[1]
2114 d[groupby] = datetime.datetime.strptime(d[groupby][:10],'%Y-%m-%d').strftime('%B %Y')
2115 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),\
2116 (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
2117 elif fget[groupby]['type'] == 'many2one':
2118 d[groupby] = d[groupby] and ((type(d[groupby])==type(1)) and d[groupby] or d[groupby][1]) or ''
2120 del alldata[d['id']][groupby]
2121 d.update(alldata[d['id']])
2125 def _inherits_join_calc(self, field, tables, where_clause):
2127 Adds missing table select and join clause(s) for reaching
2128 the field coming from an '_inherits' parent table.
2130 :param tables: list of table._table names enclosed in double quotes as returned
2134 current_table = self
2135 while field in current_table._inherit_fields and not field in current_table._columns:
2136 parent_table = self.pool.get(current_table._inherit_fields[field][0])
2137 parent_table_name = parent_table._table
2138 if '"%s"'%parent_table_name not in tables:
2139 tables.append('"%s"'%parent_table_name)
2140 where_clause.append('(%s.%s = %s.id)' % (current_table._table, current_table._inherits[parent_table._name], parent_table_name))
2141 current_table = parent_table
2142 return (tables, where_clause)
2144 def _parent_store_compute(self, cr):
2145 if not self._parent_store:
2147 logger = netsvc.Logger()
2148 logger.notifyChannel('orm', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2149 def browse_rec(root, pos=0):
2151 where = self._parent_name+'='+str(root)
2153 where = self._parent_name+' IS NULL'
2154 if self._parent_order:
2155 where += ' order by '+self._parent_order
2156 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2158 childs = cr.fetchall()
2160 pos2 = browse_rec(id[0], pos2)
2161 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos,pos2,root))
2163 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2164 if self._parent_order:
2165 query += ' order by '+self._parent_order
2168 for (root,) in cr.fetchall():
2169 pos = browse_rec(root, pos)
2172 def _update_store(self, cr, f, k):
2173 logger = netsvc.Logger()
2174 logger.notifyChannel('orm', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2175 ss = self._columns[k]._symbol_set
2176 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2177 cr.execute('select id from '+self._table)
2178 ids_lst = map(lambda x: x[0], cr.fetchall())
2181 ids_lst = ids_lst[40:]
2182 res = f.get(cr, self, iids, k, 1, {})
2183 for key,val in res.items():
2186 # if val is a many2one, just write the ID
2187 if type(val)==tuple:
2189 if (val<>False) or (type(val)<>bool):
2190 cr.execute(update_query, (ss[1](val), key))
2192 def _check_removed_columns(self, cr, log=False):
2193 logger = netsvc.Logger()
2194 # iterate on the database columns to drop the NOT NULL constraints
2195 # of fields which were required but have been removed (or will be added by another module)
2196 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2197 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2198 cr.execute("SELECT a.attname, a.attnotnull"
2199 " FROM pg_class c, pg_attribute a"
2200 " WHERE c.relname=%s"
2201 " AND c.oid=a.attrelid"
2202 " AND a.attisdropped=%s"
2203 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2204 " AND a.attname NOT IN %s" ,(self._table, False, tuple(columns))),
2206 for column in cr.dictfetchall():
2208 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))
2209 if column['attnotnull']:
2210 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2212 def _auto_init(self, cr, context={}):
2213 store_compute = False
2214 logger = netsvc.Logger()
2217 self._field_create(cr, context=context)
2218 if getattr(self, '_auto', True):
2219 cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s" ,( self._table,))
2221 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2222 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'","''")))
2225 if self._parent_store:
2226 cr.execute("""SELECT c.relname
2227 FROM pg_class c, pg_attribute a
2228 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2229 """, (self._table, 'parent_left'))
2231 if 'parent_left' not in self._columns:
2232 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, ))
2233 if 'parent_right' not in self._columns:
2234 logger.notifyChannel('orm', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, ))
2235 if self._columns[self._parent_name].ondelete != 'cascade':
2236 logger.notifyChannel('orm', netsvc.LOG_ERROR, "The column %s on object %s must be set as ondelete='cascade'" % (self._parent_name, self._name))
2237 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2238 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2240 store_compute = True
2242 if self._log_access:
2244 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2245 'create_date': 'TIMESTAMP',
2246 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2247 'write_date': 'TIMESTAMP'
2252 FROM pg_class c, pg_attribute a
2253 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2254 """, (self._table, k))
2256 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2259 self._check_removed_columns(cr, log=False)
2261 # iterate on the "object columns"
2262 todo_update_store = []
2263 update_custom_fields = context.get('update_custom_fields', False)
2264 for k in self._columns:
2265 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2267 #raise _('Can not define a column %s. Reserved keyword !') % (k,)
2268 #Not Updating Custom fields
2269 if k.startswith('x_') and not update_custom_fields:
2271 f = self._columns[k]
2273 if isinstance(f, fields.one2many):
2274 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2276 if self.pool.get(f._obj):
2277 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2278 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2279 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id,f._obj,))
2282 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))
2283 res = cr.fetchone()[0]
2285 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2286 elif isinstance(f, fields.many2many):
2287 cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,))
2288 if not cr.dictfetchall():
2289 if not self.pool.get(f._obj):
2290 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2291 ref = self.pool.get(f._obj)._table
2292 # ref = f._obj.replace('.', '_')
2293 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))
2294 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2295 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2296 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2299 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 " \
2300 "FROM pg_class c,pg_attribute a,pg_type t " \
2301 "WHERE c.relname=%s " \
2302 "AND a.attname=%s " \
2303 "AND c.oid=a.attrelid " \
2304 "AND a.atttypid=t.oid", (self._table, k))
2305 res = cr.dictfetchall()
2306 if not res and hasattr(f,'oldname'):
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, f.oldname))
2313 res_old = cr.dictfetchall()
2314 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'trying to rename %s(%s) to %s'% (self._table, f.oldname, k))
2315 if res_old and len(res_old)==1:
2316 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % ( self._table,f.oldname, k))
2318 res[0]['attname'] = k
2321 if not isinstance(f, fields.function) or f.store:
2323 # add the missing field
2324 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2325 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'","''")))
2328 if not create and k in self._defaults:
2329 if callable(self._defaults[k]):
2330 default = self._defaults[k](self, cr, 1, context)
2332 default = self._defaults[k]
2334 ss = self._columns[k]._symbol_set
2335 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2336 cr.execute(query, (ss[1](default),))
2338 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'setting default value of new column %s of table %s'% (k, self._table))
2340 logger.notifyChannel('orm', netsvc.LOG_DEBUG, 'creating new column %s of table %s'% (k, self._table))
2342 if isinstance(f, fields.function):
2344 if f.store is not True:
2345 order = f.store[f.store.keys()[0]][2]
2346 todo_update_store.append((order, f,k))
2348 # and add constraints if needed
2349 if isinstance(f, fields.many2one):
2350 if not self.pool.get(f._obj):
2351 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2352 ref = self.pool.get(f._obj)._table
2353 # ref = f._obj.replace('.', '_')
2354 # ir_actions is inherited so foreign key doesn't work on it
2355 if ref != 'ir_actions':
2356 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2358 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2362 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2364 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))
2368 f_pg_type = f_pg_def['typname']
2369 f_pg_size = f_pg_def['size']
2370 f_pg_notnull = f_pg_def['attnotnull']
2371 if isinstance(f, fields.function) and not f.store and\
2372 not getattr(f, 'nodrop', False):
2373 logger.notifyChannel('orm', netsvc.LOG_INFO, 'column %s (%s) in table %s removed: converted to a function !\n' % (k, f.string, self._table))
2374 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE'% (self._table, k))
2378 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2383 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2384 ('varchar', 'text', 'TEXT', ''),
2385 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2386 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2387 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2388 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2390 # !!! Avoid reduction of varchar field !!!
2391 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2392 # if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size:
2393 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed size" % (k, self._table))
2394 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2395 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2396 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2397 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2400 if (f_pg_type==c[0]) and (f._type==c[1]):
2401 if f_pg_type != f_obj_type:
2402 if f_pg_type != f_obj_type:
2403 logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1]))
2405 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2406 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2407 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2408 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2412 if f_pg_type != f_obj_type:
2414 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))
2416 # if the field is required and hasn't got a NOT NULL constraint
2417 if f.required and f_pg_notnull == 0:
2418 # set the field to the default value if any
2419 if k in self._defaults:
2420 if callable(self._defaults[k]):
2421 default = self._defaults[k](self, cr, 1, context)
2423 default = self._defaults[k]
2425 if (default is not None):
2426 ss = self._columns[k]._symbol_set
2427 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2428 cr.execute(query, (ss[1](default),))
2429 # add the NOT NULL constraint
2432 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2435 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))
2437 elif not f.required and f_pg_notnull == 1:
2438 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2440 indexname = '%s_%s_index' % (self._table, k)
2441 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2442 res = cr.dictfetchall()
2443 if not res and f.select:
2444 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2446 if res and not f.select:
2447 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2449 if isinstance(f, fields.many2one):
2450 ref = self.pool.get(f._obj)._table
2451 if ref != 'ir_actions':
2452 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2453 'pg_attribute as att1, pg_attribute as att2 '
2454 'WHERE con.conrelid = cl1.oid '
2455 'AND cl1.relname = %s '
2456 'AND con.confrelid = cl2.oid '
2457 'AND cl2.relname = %s '
2458 'AND array_lower(con.conkey, 1) = 1 '
2459 'AND con.conkey[1] = att1.attnum '
2460 'AND att1.attrelid = cl1.oid '
2461 'AND att1.attname = %s '
2462 'AND array_lower(con.confkey, 1) = 1 '
2463 'AND con.confkey[1] = att2.attnum '
2464 'AND att2.attrelid = cl2.oid '
2465 'AND att2.attname = %s '
2466 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2467 res = cr.dictfetchall()
2469 if res[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2470 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"')
2471 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2474 logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !"%(self._table,k))
2475 for order,f,k in todo_update_store:
2476 todo_end.append((order, self._update_store, (f, k)))
2479 cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (self._table,))
2480 create = not bool(cr.fetchone())
2482 cr.commit() # start a new transaction
2484 for (key, con, _) in self._sql_constraints:
2485 conname = '%s_%s' % (self._table, key)
2486 cr.execute("SELECT conname FROM pg_constraint where conname=%s", (conname,))
2487 if not cr.dictfetchall():
2488 query = 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,)
2493 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))
2497 if hasattr(self, "_sql"):
2498 for line in self._sql.split(';'):
2499 line2 = line.replace('\n', '').strip()
2504 self._parent_store_compute(cr)
2508 def __init__(self, cr):
2509 super(orm, self).__init__(cr)
2511 if not hasattr(self, '_log_access'):
2512 # if not access is not specify, it is the same value as _auto
2513 self._log_access = getattr(self, "_auto", True)
2515 self._columns = self._columns.copy()
2516 for store_field in self._columns:
2517 f = self._columns[store_field]
2518 if hasattr(f, 'digits_change'):
2520 if not isinstance(f, fields.function):
2524 if self._columns[store_field].store is True:
2525 sm = {self._name:(lambda self,cr, uid, ids, c={}: ids, None, 10, None)}
2527 sm = self._columns[store_field].store
2528 for object, aa in sm.items():
2530 (fnct,fields2,order,length)=aa
2532 (fnct,fields2,order)=aa
2535 raise except_orm('Error',
2536 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2537 self.pool._store_function.setdefault(object, [])
2539 for x,y,z,e,f,l in self.pool._store_function[object]:
2540 if (x==self._name) and (y==store_field) and (e==fields2):
2544 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2545 self.pool._store_function[object].sort(lambda x,y: cmp(x[4],y[4]))
2547 for (key, _, msg) in self._sql_constraints:
2548 self.pool._sql_error[self._table+'_'+key] = msg
2550 # Load manual fields
2552 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2554 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2555 for field in cr.dictfetchall():
2556 if field['name'] in self._columns:
2559 'string': field['field_description'],
2560 'required': bool(field['required']),
2561 'readonly': bool(field['readonly']),
2562 'domain': field['domain'] or None,
2563 'size': field['size'],
2564 'ondelete': field['on_delete'],
2565 'translate': (field['translate']),
2566 #'select': int(field['select_level'])
2569 if field['ttype'] == 'selection':
2570 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2571 elif field['ttype'] == 'reference':
2572 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2573 elif field['ttype'] == 'many2one':
2574 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2575 elif field['ttype'] == 'one2many':
2576 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2577 elif field['ttype'] == 'many2many':
2578 _rel1 = field['relation'].replace('.', '_')
2579 _rel2 = field['model'].replace('.', '_')
2580 _rel_name = 'x_%s_%s_%s_rel' %(_rel1, _rel2, field['name'])
2581 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2583 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2585 self._inherits_reload()
2586 if not self._sequence:
2587 self._sequence = self._table+'_id_seq'
2588 for k in self._defaults:
2589 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,)
2590 for f in self._columns:
2591 self._columns[f].restart()
2593 def default_get(self, cr, uid, fields_list, context=None):
2595 To Get default field values of given fields list of the model
2597 :param cr: database cursor
2598 :param uid: current user id
2599 :param fields_list: list of fields to get the default value
2600 :type fields_list: list (example ['field1', 'field2',])
2601 :param context: context arguments, like lang, time zone
2602 :return: dictionary of the default values for fields (set on the object class, by the user preferences, or via the context)
2608 # get the default values for the inherited fields
2609 for t in self._inherits.keys():
2610 value.update(self.pool.get(t).default_get(cr, uid, fields_list,
2613 # get the default values defined in the object
2614 for f in fields_list:
2615 if f in self._defaults:
2616 if callable(self._defaults[f]):
2617 value[f] = self._defaults[f](self, cr, uid, context)
2619 value[f] = self._defaults[f]
2621 fld_def = ((f in self._columns) and self._columns[f]) \
2622 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
2625 if isinstance(fld_def, fields.property):
2626 property_obj = self.pool.get('ir.property')
2627 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
2629 if isinstance(prop_value, (browse_record, browse_null)):
2630 value[f] = prop_value.id
2632 value[f] = prop_value
2637 # get the default values set by the user and override the default
2638 # values defined in the object
2639 ir_values_obj = self.pool.get('ir.values')
2640 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
2641 for id, field, field_value in res:
2642 if field in fields_list:
2643 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
2644 if fld_def._type in ('many2one', 'one2one'):
2645 obj = self.pool.get(fld_def._obj)
2646 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
2648 if fld_def._type in ('many2many'):
2649 obj = self.pool.get(fld_def._obj)
2651 for i in range(len(field_value)):
2652 if not obj.search(cr, uid, [('id', '=',
2655 field_value2.append(field_value[i])
2656 field_value = field_value2
2657 if fld_def._type in ('one2many'):
2658 obj = self.pool.get(fld_def._obj)
2660 for i in range(len(field_value)):
2661 field_value2.append({})
2662 for field2 in field_value[i]:
2663 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
2664 obj2 = self.pool.get(obj._columns[field2]._obj)
2665 if not obj2.search(cr, uid,
2666 [('id', '=', field_value[i][field2])]):
2668 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
2669 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
2670 if not obj2.search(cr, uid,
2671 [('id', '=', field_value[i][field2])]):
2673 # TODO add test for many2many and one2many
2674 field_value2[i][field2] = field_value[i][field2]
2675 field_value = field_value2
2676 value[field] = field_value
2677 for key in context or {}:
2678 if key.startswith('default_') and (key[8:] in fields_list):
2679 value[key[8:]] = context[key]
2683 # Update objects that uses this one to update their _inherits fields
2686 def _inherits_reload_src(self):
2687 for obj in self.pool.obj_pool.values():
2688 if self._name in obj._inherits:
2689 obj._inherits_reload()
2691 def _inherits_reload(self):
2693 for table in self._inherits:
2694 res.update(self.pool.get(table)._inherit_fields)
2695 for col in self.pool.get(table)._columns.keys():
2696 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2697 for col in self.pool.get(table)._inherit_fields.keys():
2698 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2699 self._inherit_fields = res
2700 self._inherits_reload_src()
2702 def __getattr__(self, name):
2704 Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2705 (though inherits doesn't use Python inheritance).
2706 Handles translating between local ids and remote ids.
2707 Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2708 when you have inherits.
2710 for model, field in self._inherits.iteritems():
2711 proxy = self.pool.get(model)
2712 if hasattr(proxy, name):
2713 attribute = getattr(proxy, name)
2714 if not hasattr(attribute, '__call__'):
2718 return super(orm, self).__getattr__(name)
2720 def _proxy(cr, uid, ids, *args, **kwargs):
2721 objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2722 lst = [obj[field].id for obj in objects if obj[field]]
2723 return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2728 def fields_get(self, cr, user, fields=None, context=None):
2730 Get the description of list of fields
2732 :param cr: database cursor
2733 :param user: current user id
2734 :param fields: list of fields
2735 :param context: context arguments, like lang, time zone
2736 :return: dictionary of field dictionaries, each one describing a field of the business object
2737 :raise AccessError: * if user has no create/write rights on the requested object
2740 ira = self.pool.get('ir.model.access')
2741 read_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2742 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2743 return super(orm, self).fields_get(cr, user, fields, context, read_access)
2745 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2747 Read records with given ids with the given fields
2749 :param cr: database cursor
2750 :param user: current user id
2751 :param ids: id or list of the ids of the records to read
2752 :param fields: optional list of field names to return (default: all fields would be returned)
2753 :type fields: list (example ['field_name_1', ...])
2754 :param context(optional, highly recommended): context arguments, like lang, time zone
2755 :return: list of dictionaries((dictionary per record asked)) with requested field values
2756 :rtype: [{‘name_of_the_field’: value, ...}, ...]
2757 :raise AccessError: * if user has no read rights on the requested object
2758 * if user tries to bypass access rules for read on the requested object
2763 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2765 fields = self._columns.keys() + self._inherit_fields.keys()
2766 if isinstance(ids, (int, long)):
2770 select = map(lambda x: isinstance(x,dict) and x['id'] or x, select)
2771 result = self._read_flat(cr, user, select, fields, context, load)
2774 for key, v in r.items():
2777 if key in self._columns:
2778 column = self._columns[key]
2779 elif key in self._inherit_fields:
2780 column = self._inherit_fields[key][2]
2783 if v and column._type == 'reference':
2784 model_name, ref_id = v.split(',', 1)
2785 model = self.pool.get(model_name)
2789 cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
2790 reset = not cr.fetchone()[0]
2792 if column._classic_write:
2793 query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
2794 cr.execute(query, (r['id'],))
2797 if isinstance(ids, (int, long, dict)):
2798 return result and result[0] or False
2801 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2804 #ids = map(lambda x:int(x), ids)
2807 if fields_to_read == None:
2808 fields_to_read = self._columns.keys()
2810 # construct a clause for the rules :
2811 d1, d2, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2812 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2813 fields_pre = [f for f in fields_to_read if
2814 f == self.CONCURRENCY_CHECK_FIELD
2815 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2816 ] + self._inherits.values()
2820 def convert_field(f):
2821 if f in ('create_date', 'write_date'):
2822 return "date_trunc('second', %s) as %s" % (f, f)
2823 if f == self.CONCURRENCY_CHECK_FIELD:
2824 if self._log_access:
2825 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2826 return "now()::timestamp AS %s" % (f,)
2827 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2828 return 'length("%s") as "%s"' % (f, f)
2829 return '"%s"' % (f,)
2830 fields_pre2 = map(convert_field, fields_pre)
2831 order_by = self._parent_order or self._order
2832 select_fields = ','.join(fields_pre2 + ['id'])
2833 query = 'SELECT %s FROM "%s" WHERE id in %%s' % (select_fields, self._table)
2835 query += " AND " + d1
2836 query += " ORDER BY " + order_by
2837 for sub_ids in cr.split_for_in_conditions(ids):
2839 cr.execute(query, [tuple(sub_ids)] + d2)
2840 if cr.rowcount != len(sub_ids):
2841 raise except_orm(_('AccessError'),
2842 _('You try to bypass an access rule while reading (Document type: %s).') % self._description)
2844 cr.execute(query, (tuple(sub_ids),))
2845 res.extend(cr.dictfetchall())
2847 res = map(lambda x: {'id': x}, ids)
2849 for f in fields_pre:
2850 if f == self.CONCURRENCY_CHECK_FIELD:
2852 if self._columns[f].translate:
2853 ids = map(lambda x: x['id'], res)
2854 #TODO: optimize out of this loop
2855 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2857 r[f] = res_trans.get(r['id'], False) or r[f]
2859 for table in self._inherits:
2860 col = self._inherits[table]
2861 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
2864 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
2872 if not record[col]:# if the record is deleted from _inherits table?
2874 record.update(res3[record[col]])
2875 if col not in fields_to_read:
2878 # all fields which need to be post-processed by a simple function (symbol_get)
2879 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
2882 for f in fields_post:
2883 r[f] = self._columns[f]._symbol_get(r[f])
2884 ids = map(lambda x: x['id'], res)
2886 # all non inherited fields for which the attribute whose name is in load is False
2887 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2889 # Compute POST fields
2891 for f in fields_post:
2892 todo.setdefault(self._columns[f]._multi, [])
2893 todo[self._columns[f]._multi].append(f)
2894 for key,val in todo.items():
2896 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
2899 if isinstance(res2[record['id']], str):res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
2900 record[pos] = res2[record['id']][pos]
2903 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2906 record[f] = res2[record['id']]
2910 #for f in fields_post:
2911 # # get the value of that field for all records/ids
2912 # res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
2913 # for record in res:
2914 # record[f] = res2[record['id']]
2918 for field in vals.copy():
2920 if field in self._columns:
2921 fobj = self._columns[field]
2928 for group in groups:
2929 module = group.split(".")[0]
2930 grp = group.split(".")[1]
2931 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" \
2932 (grp, module, 'res.groups', user))
2933 readonly = cr.fetchall()
2934 if readonly[0][0] >= 1:
2937 elif readonly[0][0] == 0:
2943 if type(vals[field]) == type([]):
2945 elif type(vals[field]) == type(0.0):
2947 elif type(vals[field]) == type(''):
2948 vals[field] = '=No Permission='
2953 def perm_read(self, cr, user, ids, context=None, details=True):
2955 Read the permission for record of the given ids
2957 :param cr: database cursor
2958 :param user: current user id
2959 :param ids: id or list of ids
2960 :param context: context arguments, like lang, time zone
2961 :param details: if True, \*_uid fields are replaced with the name of the user
2962 :return: list of ownership dictionaries for each requested record
2963 :rtype: list of dictionaries with the following keys:
2966 * create_uid: user who created the record
2967 * create_date: date when the record was created
2968 * write_uid: last user who changed the record
2969 * write_date: date of the last change to the record
2977 uniq = isinstance(ids, (int, long))
2981 if self._log_access:
2982 fields += ', create_uid, create_date, write_uid, write_date'
2983 query = 'SELECT %s FROM "%s" WHERE id in %%s' % (fields, self._table)
2984 cr.execute(query, (tuple(ids),))
2985 res = cr.dictfetchall()
2988 r[key] = r[key] or False
2989 if key in ('write_uid', 'create_uid', 'uid') and details:
2991 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
2996 def _check_concurrency(self, cr, ids, context):
2999 if context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access:
3001 return "%s,%s" % (self._name, oid)
3002 santa = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3003 for i in range(0, len(ids), cr.IN_MAX):
3004 sub_ids = tools.flatten(((oid, context[self.CONCURRENCY_CHECK_FIELD][key(oid)])
3005 for oid in ids[i:i+cr.IN_MAX]
3006 if key(oid) in context[self.CONCURRENCY_CHECK_FIELD]))
3008 cr.execute("SELECT count(1) FROM %s WHERE %s" % (self._table, " OR ".join([santa]*(len(sub_ids)/2))), sub_ids)
3011 raise except_orm('ConcurrencyException', _('Records were modified in the meanwhile'))
3013 def check_access_rule(self, cr, uid, ids, operation, context=None):
3014 """Verifies that the operation given by ``operation`` is allowed for the user
3015 according to ir.rules.
3017 :param operation: one of ``write``, ``unlink``
3018 :raise except_orm: * if current ir.rules do not permit this operation.
3019 :return: None if the operation is allowed
3021 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3023 where_clause = ' and ' + ' and '.join(where_clause)
3024 for sub_ids in cr.split_for_in_conditions(ids):
3025 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3026 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3027 [sub_ids] + where_params)
3028 if cr.rowcount != len(sub_ids):
3029 raise except_orm(_('AccessError'),
3030 _('Operation prohibited by access rules (Operation: %s, Document type: %s).')
3031 % (operation, self._name))
3033 def unlink(self, cr, uid, ids, context=None):
3035 Delete records with given ids
3037 :param cr: database cursor
3038 :param uid: current user id
3039 :param ids: id or list of ids
3040 :param context(optional, highly recommended): context arguments, like lang, time zone
3042 :raise AccessError: * if user has no unlink rights on the requested object
3043 * if user tries to bypass access rules for unlink on the requested object
3044 :raise UserError: if the record is default property for other records
3049 if isinstance(ids, (int, long)):
3052 result_store = self._store_get_values(cr, uid, ids, None, context)
3054 self._check_concurrency(cr, ids, context)
3056 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3058 properties = self.pool.get('ir.property')
3059 domain = [('res_id', '=', False),
3060 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3062 if properties.search(cr, uid, domain, context=context):
3063 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3065 wf_service = netsvc.LocalService("workflow")
3067 wf_service.trg_delete(uid, self._name, oid, cr)
3070 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3071 for sub_ids in cr.split_for_in_conditions(ids):
3072 cr.execute('delete from ' + self._table + ' ' \
3073 'where id in %s', (sub_ids,))
3074 for order, object, store_ids, fields in result_store:
3075 if object != self._name:
3076 obj = self.pool.get(object)
3077 cr.execute('select id from '+obj._table+' where id in %s',(tuple(store_ids),))
3078 rids = map(lambda x: x[0], cr.fetchall())
3080 obj._store_set_values(cr, uid, rids, fields, context)
3086 def write(self, cr, user, ids, vals, context=None):
3088 Update records with given ids with the given field values
3090 :param cr: database cursor
3091 :param user: current user id
3092 :type user: integer (example 1)
3093 :param ids: id or list of ids
3094 :param vals: dictionary of field values to update
3095 :type vals: dictionary (example {'field_name': 'value', ...})
3096 :param context(optional, highly recommended): context arguments, like lang, time zone
3098 :raise AccessError: * if user has no write rights on the requested object
3099 * if user tries to bypass access rules for write on the requested object
3100 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3101 :raise UserError: if recurssion is found
3103 vals format for relational field type.
3107 For write operation on a many2many fields a list of tuple is
3108 expected. The folowing tuples are accepted:
3109 (0, 0, { fields }) create
3110 (1, ID, { fields }) update (write fields to ID)
3111 (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
3112 (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
3113 (4, ID) link (add a relationship)
3115 (6, 0, list of ids) set a list of links
3119 [(6, 0, [8, 5, 6, 4])] set the many2many to ids [8, 5, 6, 4]
3121 + one2many field : [(0, 0, dictionary of values)] (example: [(0, 0, {'field_name':field_value, ...})])
3122 + many2one field : ID of related record
3123 + reference field : model name, id (example: 'product.product, 5')
3128 for field in vals.copy():
3130 if field in self._columns:
3131 fobj = self._columns[field]
3133 fobj = self._inherit_fields[field][2]
3140 for group in groups:
3141 module = group.split(".")[0]
3142 grp = group.split(".")[1]
3143 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" \
3144 (grp, module, 'res.groups', user))
3145 readonly = cr.fetchall()
3146 if readonly[0][0] >= 1:
3149 elif readonly[0][0] == 0:
3161 if isinstance(ids, (int, long)):
3164 self._check_concurrency(cr, ids, context)
3165 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3167 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3169 # No direct update of parent_left/right
3170 vals.pop('parent_left', None)
3171 vals.pop('parent_right', None)
3173 parents_changed = []
3174 if self._parent_store and (self._parent_name in vals):
3175 # The parent_left/right computation may take up to
3176 # 5 seconds. No need to recompute the values if the
3177 # parent is the same. Get the current value of the parent
3178 base_query = 'SELECT id FROM %s WHERE id IN %%s AND %s' % \
3179 (self._table, self._parent_name)
3180 params = (tuple(ids),)
3181 parent_val = vals[self._parent_name]
3183 cr.execute(base_query + " != %s", params + (parent_val,))
3185 cr.execute(base_query + " IS NULL", params)
3186 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3193 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3195 if field in self._columns:
3196 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3197 if (not totranslate) or not self._columns[field].translate:
3198 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3199 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3200 direct.append(field)
3202 upd_todo.append(field)
3204 updend.append(field)
3205 if field in self._columns \
3206 and hasattr(self._columns[field], 'selection') \
3208 if self._columns[field]._type == 'reference':
3209 val = vals[field].split(',')[0]
3212 if isinstance(self._columns[field].selection, (tuple, list)):
3213 if val not in dict(self._columns[field].selection):
3214 raise except_orm(_('ValidateError'),
3215 _('The value "%s" for the field "%s" is not in the selection') \
3216 % (vals[field], field))
3218 if val not in dict(self._columns[field].selection(
3219 self, cr, user, context=context)):
3220 raise except_orm(_('ValidateError'),
3221 _('The value "%s" for the field "%s" is not in the selection') \
3222 % (vals[field], field))
3224 if self._log_access:
3225 upd0.append('write_uid=%s')
3226 upd0.append('write_date=now()')
3230 self.check_access_rule(cr, user, ids, 'write', context=context)
3231 for sub_ids in cr.split_for_in_conditions(ids):
3232 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3233 'where id in %s', upd1 + [sub_ids])
3238 if self._columns[f].translate:
3239 src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f]
3242 # Inserting value to DB
3243 self.write(cr, user, ids, {f:vals[f]})
3244 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3247 # call the 'set' method of fields which are not classic_write
3248 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3250 # default element in context must be removed when call a one2many or many2many
3251 rel_context = context.copy()
3252 for c in context.items():
3253 if c[0].startswith('default_'):
3254 del rel_context[c[0]]
3256 for field in upd_todo:
3258 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3260 for table in self._inherits:
3261 col = self._inherits[table]
3263 for sub_ids in cr.split_for_in_conditions(ids):
3264 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3265 'where id in %s', (sub_ids,))
3266 nids.extend([x[0] for x in cr.fetchall()])
3270 if self._inherit_fields[val][0] == table:
3272 self.pool.get(table).write(cr, user, nids, v, context)
3274 self._validate(cr, user, ids, context)
3276 # TODO: use _order to set dest at the right position and not first node of parent
3277 # We can't defer parent_store computation because the stored function
3278 # fields that are computer may refer (directly or indirectly) to
3279 # parent_left/right (via a child_of domain)
3282 self.pool._init_parent[self._name]=True
3284 order = self._parent_order or self._order
3285 parent_val = vals[self._parent_name]
3287 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3289 clause, params = '%s IS NULL' % (self._parent_name,), ()
3290 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3291 parents = cr.fetchall()
3293 for id in parents_changed:
3294 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3295 pleft, pright = cr.fetchone()
3296 distance = pright - pleft + 1
3298 # Find Position of the element
3300 for (parent_pright, parent_id) in parents:
3303 position = parent_pright+1
3305 # It's the first node of the parent
3310 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3311 position = cr.fetchone()[0]+1
3313 if pleft < position <= pright:
3314 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3316 if pleft < position:
3317 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3318 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3319 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))
3321 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3322 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3323 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))
3325 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3329 for order, object, ids, fields in result:
3330 key = (object,tuple(fields))
3331 done.setdefault(key, {})
3332 # avoid to do several times the same computation
3335 if id not in done[key]:
3336 done[key][id] = True
3338 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3340 wf_service = netsvc.LocalService("workflow")
3342 wf_service.trg_write(user, self._name, id, cr)
3346 # TODO: Should set perm to user.xxx
3348 def create(self, cr, user, vals, context=None):
3350 Create new record with specified value
3352 :param cr: database cursor
3353 :param user: current user id
3354 :type user: integer (example 1)
3355 :param vals: dictionary for new record {'field_name': field_value, ...}
3356 :type vals: dictionary (example {'field_name': field_value, ...})
3357 :param context(optional, highly recommended): context arguments, like lang, time zone
3358 :type context: dictionary (example {'lang': 'en_us', ...})
3359 :return: id of new record created
3360 :raise AccessError: * if user has no create rights on the requested object
3361 * if user tries to bypass access rules for create on the requested object
3362 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3364 vals format for relational field type.
3366 + many2many field : [(6, 0, list of ids)] (example: [(6, 0, [8, 5, 6, 4])])
3367 + one2many field : [(0, 0, dictionary of values)] (example: [(0, 0, {'field_name':field_value, ...})])
3368 + many2one field : ID of related record
3369 + reference field : model name, id (example: 'product.product, 5')
3374 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3379 for (t, c) in self._inherits.items():
3381 avoid_table.append(t)
3382 for f in self._columns.keys(): # + self._inherit_fields.keys():
3386 for f in self._inherit_fields.keys():
3387 if (not f in vals) and (self._inherit_fields[f][0] not in avoid_table):
3391 default_values = self.default_get(cr, user, default, context)
3392 for dv in default_values:
3393 if dv in self._columns and self._columns[dv]._type == 'many2many':
3394 if default_values[dv] and isinstance(default_values[dv][0], (int, long)):
3395 default_values[dv] = [(6, 0, default_values[dv])]
3397 vals.update(default_values)
3400 for v in self._inherits:
3401 if self._inherits[v] not in vals:
3404 tocreate[v] = {'id' : vals[self._inherits[v]]}
3405 (upd0, upd1, upd2) = ('', '', [])
3407 for v in vals.keys():
3408 if v in self._inherit_fields:
3409 (table, col, col_detail) = self._inherit_fields[v]
3410 tocreate[table][v] = vals[v]
3413 if (v not in self._inherit_fields) and (v not in self._columns):
3416 # Try-except added to filter the creation of those records whose filds are readonly.
3417 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3419 cr.execute("SELECT nextval('"+self._sequence+"')")
3421 raise except_orm(_('UserError'),
3422 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3424 id_new = cr.fetchone()[0]
3425 for table in tocreate:
3426 if self._inherits[table] in vals:
3427 del vals[self._inherits[table]]
3429 record_id = tocreate[table].pop('id', None)
3431 if record_id is None or not record_id:
3432 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3434 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3436 upd0 += ','+self._inherits[table]
3438 upd2.append(record_id)
3440 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3441 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3443 for bool_field in bool_fields:
3444 if bool_field not in vals:
3445 vals[bool_field] = False
3447 for field in vals.copy():
3449 if field in self._columns:
3450 fobj = self._columns[field]
3452 fobj = self._inherit_fields[field][2]
3458 for group in groups:
3459 module = group.split(".")[0]
3460 grp = group.split(".")[1]
3461 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" % \
3462 (grp, module, 'res.groups', user))
3463 readonly = cr.fetchall()
3464 if readonly[0][0] >= 1:
3467 elif readonly[0][0] == 0:
3475 if self._columns[field]._classic_write:
3476 upd0 = upd0 + ',"' + field + '"'
3477 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3478 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3480 if not isinstance(self._columns[field], fields.related):
3481 upd_todo.append(field)
3482 if field in self._columns \
3483 and hasattr(self._columns[field], 'selection') \
3485 if self._columns[field]._type == 'reference':
3486 val = vals[field].split(',')[0]
3489 if isinstance(self._columns[field].selection, (tuple, list)):
3490 if val not in dict(self._columns[field].selection):
3491 raise except_orm(_('ValidateError'),
3492 _('The value "%s" for the field "%s" is not in the selection') \
3493 % (vals[field], field))
3495 if val not in dict(self._columns[field].selection(
3496 self, cr, user, context=context)):
3497 raise except_orm(_('ValidateError'),
3498 _('The value "%s" for the field "%s" is not in the selection') \
3499 % (vals[field], field))
3500 if self._log_access:
3501 upd0 += ',create_uid,create_date'
3504 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3505 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3506 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3508 if self._parent_store and not context.get('defer_parent_store_computation'):
3510 self.pool._init_parent[self._name]=True
3512 parent = vals.get(self._parent_name, False)
3514 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3516 result_p = cr.fetchall()
3517 for (pleft,) in result_p:
3522 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3523 pleft_old = cr.fetchone()[0]
3526 cr.execute('select max(parent_right) from '+self._table)
3527 pleft = cr.fetchone()[0] or 0
3528 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3529 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3530 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1,pleft+2,id_new))
3532 # default element in context must be remove when call a one2many or many2many
3533 rel_context = context.copy()
3534 for c in context.items():
3535 if c[0].startswith('default_'):
3536 del rel_context[c[0]]
3539 for field in upd_todo:
3540 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3541 self._validate(cr, user, [id_new], context)
3543 if not context.get('no_store_function', False):
3544 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3547 for order, object, ids, fields2 in result:
3548 if not (object, ids, fields2) in done:
3549 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3550 done.append((object, ids, fields2))
3552 if self._log_create and not (context and context.get('no_store_function', False)):
3553 message = self._description + \
3555 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3557 self.log(cr, user, id_new, message, True, context=context)
3558 wf_service = netsvc.LocalService("workflow")
3559 wf_service.trg_create(user, self._name, id_new, cr)
3562 def _store_get_values(self, cr, uid, ids, fields, context):
3564 fncts = self.pool._store_function.get(self._name, [])
3565 for fnct in range(len(fncts)):
3570 for f in (fields or []):
3571 if f in fncts[fnct][3]:
3577 result.setdefault(fncts[fnct][0], {})
3579 # uid == 1 for accessing objects having rules defined on store fields
3580 ids2 = fncts[fnct][2](self,cr, 1, ids, context)
3581 for id in filter(None, ids2):
3582 result[fncts[fnct][0]].setdefault(id, [])
3583 result[fncts[fnct][0]][id].append(fnct)
3585 for object in result:
3587 for id,fnct in result[object].items():
3588 k2.setdefault(tuple(fnct), [])
3589 k2[tuple(fnct)].append(id)
3590 for fnct,id in k2.items():
3591 dict.setdefault(fncts[fnct[0]][4],[])
3592 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4],object,id,map(lambda x: fncts[x][1], fnct)))
3600 def _store_set_values(self, cr, uid, ids, fields, context):
3605 if self._log_access:
3606 cr.execute('select id,write_date from '+self._table+' where id in ('+','.join(map(str, ids))+')')
3610 field_dict.setdefault(r[0], [])
3611 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3612 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3613 for i in self.pool._store_function.get(self._name, []):
3615 up_write_date = write_date + datetime.timedelta(hours=i[5])
3616 if datetime.datetime.now() < up_write_date:
3618 field_dict[r[0]].append(i[1])
3624 if self._columns[f]._multi not in keys:
3625 keys.append(self._columns[f]._multi)
3626 todo.setdefault(self._columns[f]._multi, [])
3627 todo[self._columns[f]._multi].append(f)
3631 # uid == 1 for accessing objects having rules defined on store fields
3632 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3633 for id,value in result.items():
3635 for f in value.keys():
3636 if f in field_dict[id]:
3643 if self._columns[v]._type in ('many2one', 'one2one'):
3645 value[v] = value[v][0]
3648 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3649 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3652 cr.execute('update "' + self._table + '" set ' + \
3653 string.join(upd0, ',') + ' where id = %s', upd1)
3657 # uid == 1 for accessing objects having rules defined on store fields
3658 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3659 for r in result.keys():
3661 if r in field_dict.keys():
3662 if f in field_dict[r]:
3664 for id,value in result.items():
3665 if self._columns[f]._type in ('many2one', 'one2one'):
3670 cr.execute('update "' + self._table + '" set ' + \
3671 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value),id))
3677 def perm_write(self, cr, user, ids, fields, context=None):
3678 raise NotImplementedError(_('This method does not exist anymore'))
3680 # TODO: ameliorer avec NULL
3681 def _where_calc(self, cr, user, args, active_test=True, context=None):
3685 # if the object has a field named 'active', filter out all inactive
3686 # records unless they were explicitely asked for
3687 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3689 active_in_args = False
3691 if a[0] == 'active':
3692 active_in_args = True
3693 if not active_in_args:
3694 args.insert(0, ('active', '=', 1))
3696 args = [('active', '=', 1)]
3700 e = expression.expression(args)
3701 e.parse(cr, user, self, context)
3702 tables = e.get_tables()
3703 qu1, qu2 = e.to_sql()
3704 qu1 = qu1 and [qu1] or []
3706 qu1, qu2, tables = [], [], ['"%s"' % self._table]
3708 return (qu1, qu2, tables)
3710 def _check_qorder(self, word):
3711 if not regex_order.match(word):
3712 raise except_orm(_('AccessError'), _('Bad query.'))
3715 def search(self, cr, user, args, offset=0, limit=None, order=None,
3716 context=None, count=False):
3718 Search for record/s with or without domain
3720 :param cr: database cursor
3721 :param user: current user id
3722 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
3723 :param offset: optional number from search starts
3724 :param limit: optional max number of records to return
3725 :param order: optional columns to sort by (default: self._order=id )
3726 :param context(optional, highly recommended): context arguments, like lang, time zone
3727 :param count: if True, returns only the number of records matching the criteria, not their ids
3728 :return: id or list of ids of records matching the criteria
3729 :rtype: integer or list of integers
3730 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
3733 * =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right
3735 * '&' (default), '|', '!'
3740 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3741 # compute the where, order by, limit and offset clauses
3742 (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
3743 dom = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3751 qu1 = ' where '+string.join(qu1, ' and ')
3756 order_by = self._order
3758 self._check_qorder(order)
3759 o = order.split(' ')[0]
3760 if (o in self._columns) and getattr(self._columns[o], '_classic_write'):
3763 limit_str = limit and ' limit %d' % limit or ''
3764 offset_str = offset and ' offset %d' % offset or ''
3768 cr.execute('select count(%s.id) from ' % self._table +
3769 ','.join(tables) +qu1 + limit_str + offset_str, qu2)
3772 cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2)
3774 return [x[0] for x in res]
3776 # returns the different values ever entered for one field
3777 # this is used, for example, in the client when the user hits enter on
3779 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3782 if field in self._inherit_fields:
3783 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3785 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3787 def name_get(self, cr, user, ids, context=None):
3790 :param cr: database cursor
3791 :param user: current user id
3792 :type user: integer (example 1)
3793 :param ids: list of ids
3794 :param context: context arguments, like lang, time zone
3795 :return: tuples with the text representation of requested objects for to-many relationships
3802 if isinstance(ids, (int, long)):
3804 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
3805 [self._rec_name], context, load='_classic_write')]
3807 # private implementation of name_search, allows passing a dedicated user for the name_get part to
3808 # solve some access rights issues
3809 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
3816 args += [(self._rec_name, operator, name)]
3817 ids = self.search(cr, user, args, limit=limit, context=context)
3818 res = self.name_get(cr, name_get_uid or user, ids, context)
3821 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
3824 :param cr: database cursor
3825 :param user: current user id
3826 :param name: object name to search
3827 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
3828 :param operator: operator for search criterion
3829 :param context: context arguments, like lang, time zone
3830 :param limit: optional max number of records to return
3831 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
3833 This method is equivalent of search() on name + name_get()
3836 return self._name_search(cr, user, name, args, operator, context, limit)
3838 def copy_data(self, cr, uid, id, default=None, context=None):
3840 Copy given record's data with all its fields values
3842 :param cr: database cursor
3843 :param user: current user id
3844 :param ids: id of the record to copy
3845 :param default: dictionary of field values to update before saving the duplicate object
3846 :param context: context arguments, like lang, time zone
3847 :return: dictionary containing all the field values
3854 if 'state' not in default:
3855 if 'state' in self._defaults:
3856 if callable(self._defaults['state']):
3857 default['state'] = self._defaults['state'](self, cr, uid, context)
3859 default['state'] = self._defaults['state']
3861 context_wo_lang = context
3862 if 'lang' in context:
3863 del context_wo_lang['lang']
3864 data = self.read(cr, uid, [id], context=context_wo_lang)[0]
3866 fields = self.fields_get(cr, uid, context=context)
3869 ftype = fields[f]['type']
3871 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
3875 data[f] = default[f]
3876 elif ftype == 'function':
3878 elif ftype == 'many2one':
3880 data[f] = data[f] and data[f][0]
3883 elif ftype in ('one2many', 'one2one'):
3885 rel = self.pool.get(fields[f]['relation'])
3886 if data[f] != False:
3887 for rel_id in data[f]:
3888 # the lines are first duplicated using the wrong (old)
3889 # parent but then are reassigned to the correct one thanks
3891 d,t = rel.copy_data(cr, uid, rel_id, context=context)
3892 res.append((0, 0, d))
3895 elif ftype == 'many2many':
3896 data[f] = [(6, 0, data[f])]
3898 trans_obj = self.pool.get('ir.translation')
3899 #TODO: optimize translations
3902 if f in self._columns and self._columns[f].translate:
3903 trans_name = self._name+","+f
3904 elif f in self._inherit_fields and self._inherit_fields[f][2].translate:
3905 trans_name = self._inherit_fields[f][0] + "," + f
3908 trans_ids = trans_obj.search(cr, uid, [
3909 ('name', '=', trans_name),
3910 ('res_id','=',data['id'])
3913 trans_data.extend(trans_obj.read(cr,uid,trans_ids,context=context))
3917 for v in self._inherits:
3918 del data[self._inherits[v]]
3919 return data, trans_data
3921 def copy(self, cr, uid, id, default=None, context=None):
3923 Duplicate record with given id updating it with default values
3925 :param cr: database cursor
3926 :param uid: current user id
3927 :param id: id of the record to copy
3928 :param default: dictionary of field values to update before saving the duplicate object
3929 :type default: dictionary (example {'field_name': field_value, ...})
3930 :param context: context arguments, like lang, time zone
3934 trans_obj = self.pool.get('ir.translation')
3935 data, trans_data = self.copy_data(cr, uid, id, default, context)
3936 new_id = self.create(cr, uid, data, context)
3937 for record in trans_data:
3939 record['res_id'] = new_id
3940 trans_obj.create(cr, uid, record, context)
3943 def exists(self, cr, uid, ids, context=None):
3944 if type(ids) in (int,long):
3946 query = 'SELECT count(1) FROM "%s"' % (self._table)
3947 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
3948 return cr.fetchone()[0] == len(ids)
3950 def check_recursion(self, cr, uid, ids, parent=None):
3952 Check recursion in records
3954 :param cr: database cursor
3955 :param uid: current user id
3956 :param ids: list of ids of records
3957 :param parent: parent field name
3958 :return: True or False based on recursion detection
3962 parent = self._parent_name
3964 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
3967 for i in range(0, len(ids), cr.IN_MAX):
3968 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
3969 cr.execute(query, (tuple(sub_ids_parent),))
3970 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
3971 ids_parent = ids_parent2
3972 for i in ids_parent:
3977 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
3978 """Find out the XML ID of any database record, if there
3979 is one. This method works as a possible implementation
3980 for a function field, to be able to add it to any
3981 model object easily, referencing it as 'osv.osv.get_xml_id'.
3983 get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }
3985 :return: the fully qualified XML ID of the given object,
3986 defaulting to an empty string when there's none.
3988 result = dict.fromkeys(ids, '')
3989 model_data_obj = self.pool.get('ir.model.data')
3990 data_ids = model_data_obj.search(cr,uid,
3991 [('model','=',self._name),('res_id','in',ids)])
3992 data_results = model_data_obj.read(cr,uid,data_ids,
3993 ['name','module','res_id'])
3994 for record in data_results:
3995 result[record['res_id']] = '%(module)s.%(name)s' % record
3998 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: