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