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 _
58 from query import Query
60 from tools.safe_eval import safe_eval as eval
62 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
65 POSTGRES_CONFDELTYPES = {
73 def last_day_of_current_month():
74 today = datetime.date.today()
75 last_day = str(calendar.monthrange(today.year, today.month)[1])
76 return time.strftime('%Y-%m-' + last_day)
78 def intersect(la, lb):
79 return filter(lambda x: x in lb, la)
81 class except_orm(Exception):
82 def __init__(self, name, value):
85 self.args = (name, value)
87 class BrowseRecordError(Exception):
90 # Readonly python database object browser
91 class browse_null(object):
96 def __getitem__(self, name):
99 def __getattr__(self, name):
100 return None # XXX: return self ?
108 def __nonzero__(self):
111 def __unicode__(self):
116 # TODO: execute an object method on browse_record_list
118 class browse_record_list(list):
120 def __init__(self, lst, context=None):
123 super(browse_record_list, self).__init__(lst)
124 self.context = context
127 class browse_record(object):
128 logger = netsvc.Logger()
130 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
132 table : the object (inherited from orm)
133 context : dictionary with an optional context
135 if fields_process is None:
139 self._list_class = list_class or browse_record_list
144 self._table_name = self._table._name
145 self.__logger = logging.getLogger(
146 'osv.browse_record.' + self._table_name)
147 self._context = context
148 self._fields_process = fields_process
150 cache.setdefault(table._name, {})
151 self._data = cache[table._name]
153 if not (id and isinstance(id, (int, long,))):
154 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
155 # if not table.exists(cr, uid, id, context):
156 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
158 if id not in self._data:
159 self._data[id] = {'id': id}
163 def __getitem__(self, name):
167 if name not in self._data[self._id]:
168 # build the list of fields we will fetch
170 # fetch the definition of the field which was asked for
171 if name in self._table._columns:
172 col = self._table._columns[name]
173 elif name in self._table._inherit_fields:
174 col = self._table._inherit_fields[name][2]
175 elif hasattr(self._table, str(name)):
176 attr = getattr(self._table, name)
178 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
179 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
183 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
184 "Field '%s' does not exist in object '%s': \n%s" % (
185 name, self, ''.join(traceback.format_exc())))
186 raise KeyError("Field '%s' does not exist in object '%s'" % (
189 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
191 # gen the list of "local" (ie not inherited) fields which are classic or many2one
192 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
193 # gen the list of inherited fields
194 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
195 # complete the field list with the inherited fields which are classic or many2one
196 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
197 # otherwise we fetch only that field
199 fields_to_fetch = [(name, col)]
200 ids = filter(lambda id: name not in self._data[id], self._data.keys())
202 field_names = map(lambda x: x[0], fields_to_fetch)
203 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
204 if self._fields_process:
205 lang = self._context.get('lang', 'en_US') or 'en_US'
206 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
208 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
209 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
211 for field_name, field_column in fields_to_fetch:
212 if field_column._type in self._fields_process:
213 for result_line in field_values:
214 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
215 if result_line[field_name]:
216 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
219 # Where did those ids come from? Perhaps old entries in ir_model_dat?
220 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
221 raise KeyError('Field %s not found in %s'%(name, self))
222 # create browse records for 'remote' objects
223 for result_line in field_values:
225 for field_name, field_column in fields_to_fetch:
226 if field_column._type in ('many2one', 'one2one'):
227 if result_line[field_name]:
228 obj = self._table.pool.get(field_column._obj)
229 if isinstance(result_line[field_name], (list, tuple)):
230 value = result_line[field_name][0]
232 value = result_line[field_name]
234 # FIXME: this happen when a _inherits object
235 # overwrite a field of it parent. Need
236 # testing to be sure we got the right
237 # object and not the parent one.
238 if not isinstance(value, browse_record):
239 new_data[field_name] = browse_record(self._cr,
240 self._uid, value, obj, self._cache,
241 context=self._context,
242 list_class=self._list_class,
243 fields_process=self._fields_process)
245 new_data[field_name] = value
247 new_data[field_name] = browse_null()
249 new_data[field_name] = browse_null()
250 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
251 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)
252 elif field_column._type in ('reference'):
253 if result_line[field_name]:
254 if isinstance(result_line[field_name], browse_record):
255 new_data[field_name] = result_line[field_name]
257 ref_obj, ref_id = result_line[field_name].split(',')
258 ref_id = long(ref_id)
259 obj = self._table.pool.get(ref_obj)
260 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)
262 new_data[field_name] = browse_null()
264 new_data[field_name] = result_line[field_name]
265 self._data[result_line['id']].update(new_data)
267 if not name in self._data[self._id]:
268 #how did this happen?
269 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
270 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
271 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
272 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
273 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
274 return self._data[self._id][name]
276 def __getattr__(self, name):
280 raise AttributeError(e)
282 def __contains__(self, name):
283 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
285 def __hasattr__(self, name):
292 return "browse_record(%s, %d)" % (self._table_name, self._id)
294 def __eq__(self, other):
295 if not isinstance(other, browse_record):
297 return (self._table_name, self._id) == (other._table_name, other._id)
299 def __ne__(self, other):
300 if not isinstance(other, browse_record):
302 return (self._table_name, self._id) != (other._table_name, other._id)
304 # we need to define __unicode__ even though we've already defined __str__
305 # because we have overridden __getattr__
306 def __unicode__(self):
307 return unicode(str(self))
310 return hash((self._table_name, self._id))
318 (type returned by postgres when the column was created, type expression to create the column)
322 fields.boolean: 'bool',
323 fields.integer: 'int4',
324 fields.integer_big: 'int8',
328 fields.datetime: 'timestamp',
329 fields.binary: 'bytea',
330 fields.many2one: 'int4',
332 if type(f) in type_dict:
333 f_type = (type_dict[type(f)], type_dict[type(f)])
334 elif isinstance(f, fields.float):
336 f_type = ('numeric', 'NUMERIC')
338 f_type = ('float8', 'DOUBLE PRECISION')
339 elif isinstance(f, (fields.char, fields.reference)):
340 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
341 elif isinstance(f, fields.selection):
342 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
343 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
344 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
347 f_size = getattr(f, 'size', None) or 16
350 f_type = ('int4', 'INTEGER')
352 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
353 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
354 t = eval('fields.'+(f._type), globals())
355 f_type = (type_dict[t], type_dict[t])
356 elif isinstance(f, fields.function) and f._type == 'float':
358 f_type = ('numeric', 'NUMERIC')
360 f_type = ('float8', 'DOUBLE PRECISION')
361 elif isinstance(f, fields.function) and f._type == 'selection':
362 f_type = ('text', 'text')
363 elif isinstance(f, fields.function) and f._type == 'char':
364 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
366 logger = netsvc.Logger()
367 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
372 class orm_template(object):
378 _parent_name = 'parent_id'
379 _parent_store = False
380 _parent_order = False
390 CONCURRENCY_CHECK_FIELD = '__last_update'
391 def log(self, cr, uid, id, message, secondary=False, context=None):
392 return self.pool.get('res.log').create(cr, uid,
395 'res_model': self._name,
396 'secondary': secondary,
403 def view_init(self, cr, uid, fields_list, context=None):
404 """Override this method to do specific things when a view on the object is opened."""
407 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
408 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
410 def _field_create(self, cr, context=None):
413 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
415 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
416 model_id = cr.fetchone()[0]
417 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'))
419 model_id = cr.fetchone()[0]
420 if 'module' in context:
421 name_id = 'model_'+self._name.replace('.', '_')
422 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id, model_id, context['module']))
424 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
425 (name_id, context['module'], 'ir.model', model_id)
430 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
432 for rec in cr.dictfetchall():
433 cols[rec['name']] = rec
435 for (k, f) in self._columns.items():
437 'model_id': model_id,
440 'field_description': f.string.replace("'", " "),
442 'relation': f._obj or '',
443 'view_load': (f.view_load and 1) or 0,
444 'select_level': tools.ustr(f.select or 0),
445 'readonly': (f.readonly and 1) or 0,
446 'required': (f.required and 1) or 0,
447 'selectable': (f.selectable and 1) or 0,
448 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
450 # When its a custom field,it does not contain f.select
451 if context.get('field_state', 'base') == 'manual':
452 if context.get('field_name', '') == k:
453 vals['select_level'] = context.get('select', '0')
454 #setting value to let the problem NOT occur next time
456 vals['select_level'] = cols[k]['select_level']
459 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
460 id = cr.fetchone()[0]
462 cr.execute("""INSERT INTO ir_model_fields (
463 id, model_id, model, name, field_description, ttype,
464 relation,view_load,state,select_level,relation_field
466 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
468 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
469 vals['relation'], bool(vals['view_load']), 'base',
470 vals['select_level'], vals['relation_field']
472 if 'module' in context:
473 name1 = 'field_' + self._table + '_' + k
474 cr.execute("select name from ir_model_data where name=%s", (name1,))
476 name1 = name1 + "_" + str(id)
477 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
478 (name1, context['module'], 'ir.model.fields', id)
481 for key, val in vals.items():
482 if cols[k][key] != vals[key]:
483 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
485 cr.execute("""UPDATE ir_model_fields SET
486 model_id=%s, field_description=%s, ttype=%s, relation=%s,
487 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
489 model=%s AND name=%s""", (
490 vals['model_id'], vals['field_description'], vals['ttype'],
491 vals['relation'], bool(vals['view_load']),
492 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
497 def _auto_init(self, cr, context=None):
498 self._field_create(cr, context=context)
500 def __init__(self, cr):
501 if not self._name and not hasattr(self, '_inherit'):
502 name = type(self).__name__.split('.')[0]
503 msg = "The class %s has to have a _name attribute" % name
505 logger = netsvc.Logger()
506 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
507 raise except_orm('ValueError', msg)
509 if not self._description:
510 self._description = self._name
512 self._table = self._name.replace('.', '_')
514 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
516 Fetch records as objects allowing to use dot notation to browse fields and relations
518 :param cr: database cursor
519 :param user: current user id
520 :param select: id or list of ids
521 :param context: context arguments, like lang, time zone
522 :rtype: object or list of objects requested
525 self._list_class = list_class or browse_record_list
527 # need to accepts ints and longs because ids coming from a method
528 # launched by button in the interface have a type long...
529 if isinstance(select, (int, long)):
530 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
531 elif isinstance(select, list):
532 return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context=context)
536 def __export_row(self, cr, uid, row, fields, context=None):
540 def check_type(field_type):
541 if field_type == 'float':
543 elif field_type == 'integer':
545 elif field_type == 'boolean':
549 def selection_field(in_field):
550 col_obj = self.pool.get(in_field.keys()[0])
551 if f[i] in col_obj._columns.keys():
552 return col_obj._columns[f[i]]
553 elif f[i] in col_obj._inherits.keys():
554 selection_field(col_obj._inherits)
559 data = map(lambda x: '', range(len(fields)))
561 for fpos in range(len(fields)):
570 model_data = self.pool.get('ir.model.data')
571 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
573 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
575 r = '%s.%s' % (d['module'], d['name'])
582 # To display external name of selection field when its exported
583 if not context.get('import_comp', False):# Allow external name only if its not import compatible
585 if f[i] in self._columns.keys():
586 cols = self._columns[f[i]]
587 elif f[i] in self._inherit_fields.keys():
588 cols = selection_field(self._inherits)
589 if cols and cols._type == 'selection':
590 sel_list = cols.selection
591 if r and type(sel_list) == type([]):
592 r = [x[1] for x in sel_list if r==x[0]]
593 r = r and r[0] or False
595 if f[i] in self._columns:
596 r = check_type(self._columns[f[i]]._type)
597 elif f[i] in self._inherit_fields:
598 r = check_type(self._inherit_fields[f[i]][2]._type)
601 if isinstance(r, (browse_record_list, list)):
603 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
606 if [x for x in fields2 if x]:
610 lines2 = self.__export_row(cr, uid, row2, fields2,
613 for fpos2 in range(len(fields)):
614 if lines2 and lines2[0][fpos2]:
615 data[fpos2] = lines2[0][fpos2]
619 if isinstance(rr.name, browse_record):
621 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
622 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
623 dt += tools.ustr(rr_name or '') + ','
633 if isinstance(r, browse_record):
634 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
635 r = r and r[0] and r[0][1] or ''
636 data[fpos] = tools.ustr(r or '')
637 return [data] + lines
639 def export_data(self, cr, uid, ids, fields_to_export, context=None):
641 Export fields for selected objects
643 :param cr: database cursor
644 :param uid: current user id
645 :param ids: list of ids
646 :param fields_to_export: list of fields
647 :param context: context arguments, like lang, time zone, may contain import_comp(default: False) to make exported data compatible with import_data()
648 :rtype: dictionary with a *datas* matrix
650 This method is used when exporting data via client menu
655 imp_comp = context.get('import_comp', False)
656 cols = self._columns.copy()
657 for f in self._inherit_fields:
658 cols.update({f: self._inherit_fields[f][2]})
659 fields_to_export = map(lambda x: x.split('/'), fields_to_export)
660 fields_export = fields_to_export + []
663 for field in fields_export:
664 if imp_comp and len(field) > 1:
665 warning_fields.append('/'.join(map(lambda x: x in cols and cols[x].string or x,field)))
666 elif len (field) <= 1:
667 if imp_comp and cols.get(field and field[0], False):
668 if ((isinstance(cols[field[0]], fields.function) and not cols[field[0]].store) \
669 or isinstance(cols[field[0]], fields.related)\
670 or isinstance(cols[field[0]], fields.one2many)):
671 warning_fields.append('/'.join(map(lambda x: x in cols and cols[x].string or x,field)))
673 if imp_comp and len(warning_fields):
674 warning = 'Following columns cannot be exported since you select to be import compatible.\n%s' % ('\n'.join(warning_fields))
676 return {'warning': warning}
677 for row in self.browse(cr, uid, ids, context):
678 datas += self.__export_row(cr, uid, row, fields_to_export, context)
679 return {'datas': datas}
681 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
683 Import given data in given module
685 :param cr: database cursor
686 :param uid: current user id
687 :param ids: list of ids
688 :param fields: list of fields
689 :param data: data to import
690 :param mode: 'init' or 'update' for record creation
691 :param current_module: module name
692 :param noupdate: flag for record creation
693 :param context: context arguments, like lang, time zone,
694 :param filename: optional file to store partial import state for recovery
697 This method is used when importing data via client menu
702 fields = map(lambda x: x.split('/'), fields)
703 logger = netsvc.Logger()
704 ir_model_data_obj = self.pool.get('ir.model.data')
706 def _check_db_id(self, model_name, db_id):
707 obj_model = self.pool.get(model_name)
708 ids = obj_model.search(cr, uid, [('id', '=', int(db_id))])
710 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
713 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
714 line = datas[position]
723 ir_model_data_obj = self.pool.get('ir.model.data')
725 # Import normal fields
727 for i in range(len(fields)):
729 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
734 if prefix and not prefix[0] in field:
737 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':db_id'):
741 field_name = field[0].split(':')[0]
742 model_rel = fields_def[field_name]['relation']
744 if fields_def[field[len(prefix)][:-6]]['type'] == 'many2many':
746 for db_id in line[i].split(config.get('csv_internal_sep')):
748 _check_db_id(self, model_rel, db_id)
751 warning += [tools.exception_to_unicode(e)]
752 logger.notifyChannel("import", netsvc.LOG_ERROR,
753 tools.exception_to_unicode(e))
755 res = [(6, 0, res_id)]
758 _check_db_id(self, model_rel, line[i])
761 warning += [tools.exception_to_unicode(e)]
762 logger.notifyChannel("import", netsvc.LOG_ERROR,
763 tools.exception_to_unicode(e))
764 row[field_name] = res or False
767 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
770 if fields_def[field[len(prefix)][:-3]]['type'] == 'many2many':
772 for word in line[i].split(config.get('csv_internal_sep')):
774 module, xml_id = word.rsplit('.', 1)
776 module, xml_id = current_module, word
777 id = ir_model_data_obj._get_id(cr, uid, module,
779 res_id2 = ir_model_data_obj.read(cr, uid, [id],
780 ['res_id'])[0]['res_id']
782 res_id.append(res_id2)
784 res_id = [(6, 0, res_id)]
787 module, xml_id = line[i].rsplit('.', 1)
789 module, xml_id = current_module, line[i]
790 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
791 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
793 res_id = ir_model_data[0]['res_id']
795 raise ValueError('No references to %s.%s' % (module, xml_id))
796 row[field[-1][:-3]] = res_id or False
798 if (len(field) == len(prefix)+1) and \
799 len(field[len(prefix)].split(':lang=')) == 2:
800 f, lang = field[len(prefix)].split(':lang=')
801 translate.setdefault(lang, {})[f] = line[i] or False
803 if (len(field) == len(prefix)+1) and \
804 (prefix == field[0:len(prefix)]):
805 if field[len(prefix)] == "id":
808 is_xml_id = data_id = line[i]
809 d = data_id.split('.')
810 module = len(d) > 1 and d[0] or ''
811 name = len(d) > 1 and d[1] or d[0]
812 data_ids = ir_model_data_obj.search(cr, uid, [('module', '=', module), ('model', '=', model_name), ('name', '=', name)])
814 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
816 if is_db_id and not db_id:
817 data_ids = ir_model_data_obj.search(cr, uid, [('module', '=', module), ('model', '=', model_name), ('res_id', '=', is_db_id)])
818 if not len(data_ids):
819 ir_model_data_obj.create(cr, uid, {'module': module, 'model': model_name, 'name': name, 'res_id': is_db_id})
821 if is_db_id and int(db_id) != int(is_db_id):
822 warning += [_("Id is not the same than existing one: %s") % (is_db_id)]
823 logger.notifyChannel("import", netsvc.LOG_ERROR,
824 _("Id is not the same than existing one: %s") % (is_db_id))
827 if field[len(prefix)] == "db_id":
830 _check_db_id(self, model_name, line[i])
831 data_res_id = is_db_id = int(line[i])
833 warning += [tools.exception_to_unicode(e)]
834 logger.notifyChannel("import", netsvc.LOG_ERROR,
835 tools.exception_to_unicode(e))
837 data_ids = ir_model_data_obj.search(cr, uid, [('model', '=', model_name), ('res_id', '=', line[i])])
839 d = ir_model_data_obj.read(cr, uid, data_ids, ['name', 'module'])[0]
842 data_id = '%s.%s' % (d['module'], d['name'])
845 if is_xml_id and not data_id:
847 if is_xml_id and is_xml_id != data_id:
848 warning += [_("Id is not the same than existing one: %s") % (line[i])]
849 logger.notifyChannel("import", netsvc.LOG_ERROR,
850 _("Id is not the same than existing one: %s") % (line[i]))
853 if fields_def[field[len(prefix)]]['type'] == 'integer':
854 res = line[i] and int(line[i])
855 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
856 res = line[i].lower() not in ('0', 'false', 'off')
857 elif fields_def[field[len(prefix)]]['type'] == 'float':
858 res = line[i] and float(line[i])
859 elif fields_def[field[len(prefix)]]['type'] == 'selection':
861 if isinstance(fields_def[field[len(prefix)]]['selection'],
863 sel = fields_def[field[len(prefix)]]['selection']
865 sel = fields_def[field[len(prefix)]]['selection'](self,
868 if line[i] in [tools.ustr(key), tools.ustr(val)]: #Acepting key or value for selection field
871 if line[i] and not res:
872 logger.notifyChannel("import", netsvc.LOG_WARNING,
873 _("key '%s' not found in selection field '%s'") % \
874 (line[i], field[len(prefix)]))
876 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
878 elif fields_def[field[len(prefix)]]['type'] == 'many2one':
881 relation = fields_def[field[len(prefix)]]['relation']
882 res2 = self.pool.get(relation).name_search(cr, uid,
883 line[i], [], operator='=', context=context)
884 res = (res2 and res2[0][0]) or False
886 warning += [_("Relation not found: %s on '%s'") % (line[i], relation)]
887 logger.notifyChannel("import", netsvc.LOG_WARNING,
888 _("Relation not found: %s on '%s'") % (line[i], relation))
889 elif fields_def[field[len(prefix)]]['type'] == 'many2many':
892 relation = fields_def[field[len(prefix)]]['relation']
893 for word in line[i].split(config.get('csv_internal_sep')):
894 res2 = self.pool.get(relation).name_search(cr,
895 uid, word, [], operator='=', context=context)
896 res3 = (res2 and res2[0][0]) or False
898 warning += [_("Relation not found: %s on '%s'") % (line[i], relation)]
899 logger.notifyChannel("import",
901 _("Relation not found: %s on '%s'") % (line[i], relation))
907 res = line[i] or False
908 row[field[len(prefix)]] = res
909 elif (prefix==field[0:len(prefix)]):
910 if field[0] not in todo:
911 todo.append(field[len(prefix)])
913 # Import one2many, many2many fields
917 relation_obj = self.pool.get(fields_def[field]['relation'])
918 newfd = relation_obj.fields_get(
919 cr, uid, context=context)
920 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
921 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
922 nbrmax = max(nbrmax, max2)
923 warning = warning + w2
924 reduce(lambda x, y: x and y, newrow)
925 row[field] = newrow and (reduce(lambda x, y: x or y, newrow.values()) and \
926 [(0, 0, newrow)]) or []
928 while (position+i) < len(datas):
930 for j in range(len(fields)):
932 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
937 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
938 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
939 warning = warning + w2
940 if newrow and reduce(lambda x, y: x or y, newrow.values()):
941 row[field].append((0, 0, newrow))
943 nbrmax = max(nbrmax, i)
946 for i in range(max(nbrmax, 1)):
949 result = (row, nbrmax, warning, translate, data_id, data_res_id)
952 fields_def = self.fields_get(cr, uid, context=context)
955 initial_size = len(datas)
956 if config.get('import_partial', False) and filename:
957 data = pickle.load(file(config.get('import_partial')))
958 original_value = data.get(filename, 0)
964 (res, other, warning, translate, data_id, res_id) = \
965 process_liness(self, datas, [], current_module, self._name, fields_def)
968 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
971 id = ir_model_data_obj._update(cr, uid, self._name,
972 current_module, res, xml_id=data_id, mode=mode,
973 noupdate=noupdate, res_id=res_id, context=context)
978 if isinstance(e, psycopg2.IntegrityError):
979 msg = _('Insertion Failed! ')
980 for key in self.pool._sql_error.keys():
982 msg = self.pool._sql_error[key]
983 if hasattr(msg, '__call__'):
984 msg = msg(cr, uid, [res_id,], context=context)
988 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '')
989 if isinstance(e, osv.orm.except_orm):
990 msg = _('Insertion Failed! ' + e[1])
991 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '')
992 #Raising Uncaught exception
993 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '')
995 for lang in translate:
996 context2 = context.copy()
997 context2['lang'] = lang
998 self.write(cr, uid, [id], translate[lang], context2)
999 if config.get('import_partial', False) and filename and (not (counter%100)):
1000 data = pickle.load(file(config.get('import_partial')))
1001 data[filename] = initial_size - len(datas) + original_value
1002 pickle.dump(data, file(config.get('import_partial'), 'wb'))
1003 if context.get('defer_parent_store_computation'):
1004 self._parent_store_compute(cr)
1007 #except Exception, e:
1008 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1011 # return (-1, res, e[0], warning)
1013 # return (-1, res, e[0], '')
1016 # TODO: Send a request with the result and multi-thread !
1018 if context.get('defer_parent_store_computation'):
1019 self._parent_store_compute(cr)
1020 return (done, 0, 0, 0)
1022 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1024 Read records with given ids with the given fields
1026 :param cr: database cursor
1027 :param user: current user id
1028 :param ids: id or list of the ids of the records to read
1029 :param fields: optional list of field names to return (default: all fields would be returned)
1030 :type fields: list (example ['field_name_1', ...])
1031 :param context: optional context dictionary - it may contains keys for specifying certain options
1032 like ``context_lang``, ``context_tz`` to alter the results of the call.
1033 A special ``bin_size`` boolean flag may also be passed in the context to request the
1034 value of all fields.binary columns to be returned as the size of the binary instead of its
1035 contents. This can also be selectively overriden by passing a field-specific flag
1036 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1037 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1038 :return: list of dictionaries((dictionary per record asked)) with requested field values
1039 :rtype: [{‘name_of_the_field’: value, ...}, ...]
1040 :raise AccessError: * if user has no read rights on the requested object
1041 * if user tries to bypass access rules for read on the requested object
1044 raise NotImplementedError(_('The read method is not implemented on this object !'))
1046 def get_invalid_fields(self, cr, uid):
1047 return list(self._invalids)
1049 def _validate(self, cr, uid, ids, context=None):
1050 context = context or {}
1051 lng = context.get('lang', False) or 'en_US'
1052 trans = self.pool.get('ir.translation')
1054 for constraint in self._constraints:
1055 fun, msg, fields = constraint
1056 if not fun(self, cr, uid, ids):
1057 # Check presence of __call__ directly instead of using
1058 # callable() because it will be deprecated as of Python 3.0
1059 if hasattr(msg, '__call__'):
1060 tmp_msg = msg(self, cr, uid, ids, context=context)
1061 # Why translate something that has been generated dynamically?
1062 # tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg
1063 if isinstance(tmp_msg, tuple):
1064 tmp_msg, params = tmp_msg
1065 translated_msg = tmp_msg % params
1067 translated_msg = tmp_msg
1069 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1071 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1073 self._invalids.update(fields)
1076 raise except_orm('ValidateError', '\n'.join(error_msgs))
1078 self._invalids.clear()
1080 def default_get(self, cr, uid, fields_list, context=None):
1082 Returns default values for the fields in fields_list.
1084 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1085 :type fields_list: list
1086 :param context: optional context dictionary - it may contains keys for specifying certain options
1087 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1088 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1089 or override a default value for a field.
1090 A special ``bin_size`` boolean flag may also be passed in the context to request the
1091 value of all fields.binary columns to be returned as the size of the binary instead of its
1092 contents. This can also be selectively overriden by passing a field-specific flag
1093 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1094 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1095 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1097 # trigger view init hook
1098 self.view_init(cr, uid, fields_list, context)
1104 # get the default values for the inherited fields
1105 for t in self._inherits.keys():
1106 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1109 # get the default values defined in the object
1110 for f in fields_list:
1111 if f in self._defaults:
1112 if callable(self._defaults[f]):
1113 defaults[f] = self._defaults[f](self, cr, uid, context)
1115 defaults[f] = self._defaults[f]
1117 fld_def = ((f in self._columns) and self._columns[f]) \
1118 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1121 if isinstance(fld_def, fields.property):
1122 property_obj = self.pool.get('ir.property')
1123 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1125 if isinstance(prop_value, (browse_record, browse_null)):
1126 defaults[f] = prop_value.id
1128 defaults[f] = prop_value
1130 if f not in defaults:
1133 # get the default values set by the user and override the default
1134 # values defined in the object
1135 ir_values_obj = self.pool.get('ir.values')
1136 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1137 for id, field, field_value in res:
1138 if field in fields_list:
1139 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1140 if fld_def._type in ('many2one', 'one2one'):
1141 obj = self.pool.get(fld_def._obj)
1142 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1144 if fld_def._type in ('many2many'):
1145 obj = self.pool.get(fld_def._obj)
1147 for i in range(len(field_value)):
1148 if not obj.search(cr, uid, [('id', '=',
1151 field_value2.append(field_value[i])
1152 field_value = field_value2
1153 if fld_def._type in ('one2many'):
1154 obj = self.pool.get(fld_def._obj)
1156 for i in range(len(field_value)):
1157 field_value2.append({})
1158 for field2 in field_value[i]:
1159 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1160 obj2 = self.pool.get(obj._columns[field2]._obj)
1161 if not obj2.search(cr, uid,
1162 [('id', '=', field_value[i][field2])]):
1164 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1165 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1166 if not obj2.search(cr, uid,
1167 [('id', '=', field_value[i][field2])]):
1169 # TODO add test for many2many and one2many
1170 field_value2[i][field2] = field_value[i][field2]
1171 field_value = field_value2
1172 defaults[field] = field_value
1174 # get the default values from the context
1175 for key in context or {}:
1176 if key.startswith('default_') and (key[8:] in fields_list):
1177 defaults[key[8:]] = context[key]
1181 def perm_read(self, cr, user, ids, context=None, details=True):
1182 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1184 def unlink(self, cr, uid, ids, context=None):
1185 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1187 def write(self, cr, user, ids, vals, context=None):
1188 raise NotImplementedError(_('The write method is not implemented on this object !'))
1190 def create(self, cr, user, vals, context=None):
1191 raise NotImplementedError(_('The create method is not implemented on this object !'))
1193 def fields_get_keys(self, cr, user, context=None):
1194 res = self._columns.keys()
1195 for parent in self._inherits:
1196 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1199 # returns the definition of each field in the object
1200 # the optional fields parameter can limit the result to some fields
1201 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1205 translation_obj = self.pool.get('ir.translation')
1206 for parent in self._inherits:
1207 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1209 if self._columns.keys():
1210 for f in self._columns.keys():
1211 if allfields and f not in allfields:
1213 res[f] = {'type': self._columns[f]._type}
1214 # This additional attributes for M2M and function field is added
1215 # because we need to display tooltip with this additional information
1216 # when client is started in debug mode.
1217 if isinstance(self._columns[f], fields.function):
1218 res[f]['function'] = self._columns[f]._fnct and self._columns[f]._fnct.func_name or False
1219 res[f]['store'] = self._columns[f].store
1220 if isinstance(self._columns[f].store, dict):
1221 res[f]['store'] = str(self._columns[f].store)
1222 res[f]['fnct_search'] = self._columns[f]._fnct_search and self._columns[f]._fnct_search.func_name or False
1223 res[f]['fnct_inv'] = self._columns[f]._fnct_inv and self._columns[f]._fnct_inv.func_name or False
1224 res[f]['fnct_inv_arg'] = self._columns[f]._fnct_inv_arg or False
1225 res[f]['func_obj'] = self._columns[f]._obj or False
1226 res[f]['func_method'] = self._columns[f]._method
1227 if isinstance(self._columns[f], fields.many2many):
1228 res[f]['related_columns'] = list((self._columns[f]._id1, self._columns[f]._id2))
1229 res[f]['third_table'] = self._columns[f]._rel
1230 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1231 'change_default', 'translate', 'help', 'select', 'selectable'):
1232 if getattr(self._columns[f], arg):
1233 res[f][arg] = getattr(self._columns[f], arg)
1234 if not write_access:
1235 res[f]['readonly'] = True
1236 res[f]['states'] = {}
1237 for arg in ('digits', 'invisible', 'filters'):
1238 if getattr(self._columns[f], arg, None):
1239 res[f][arg] = getattr(self._columns[f], arg)
1241 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', self._columns[f].string)
1243 res[f]['string'] = res_trans
1244 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1246 res[f]['help'] = help_trans
1248 if hasattr(self._columns[f], 'selection'):
1249 if isinstance(self._columns[f].selection, (tuple, list)):
1250 sel = self._columns[f].selection
1251 # translate each selection option
1253 for (key, val) in sel:
1256 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1257 sel2.append((key, val2 or val))
1259 res[f]['selection'] = sel
1261 # call the 'dynamic selection' function
1262 res[f]['selection'] = self._columns[f].selection(self, cr,
1264 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1265 res[f]['relation'] = self._columns[f]._obj
1266 res[f]['domain'] = self._columns[f]._domain
1267 res[f]['context'] = self._columns[f]._context
1269 #TODO : read the fields from the database
1273 # filter out fields which aren't in the fields list
1274 for r in res.keys():
1275 if r not in allfields:
1280 # Overload this method if you need a window title which depends on the context
1282 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1285 def __view_look_dom(self, cr, user, node, view_id, context=None):
1293 if isinstance(s, unicode):
1294 return s.encode('utf8')
1297 # return True if node can be displayed to current user
1298 def check_group(node):
1299 if node.get('groups'):
1300 groups = node.get('groups').split(',')
1301 access_pool = self.pool.get('ir.model.access')
1302 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1304 node.set('invisible', '1')
1305 if 'attrs' in node.attrib:
1306 del(node.attrib['attrs']) #avoid making field visible later
1307 del(node.attrib['groups'])
1312 if node.tag in ('field', 'node', 'arrow'):
1313 if node.get('object'):
1318 if f.tag in ('field'):
1319 xml += etree.tostring(f, encoding="utf-8")
1321 new_xml = etree.fromstring(encode(xml))
1322 ctx = context.copy()
1323 ctx['base_model_name'] = self._name
1324 xarch, xfields = self.pool.get(node.get('object', False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1325 views[str(f.tag)] = {
1329 attrs = {'views': views}
1330 fields = views.get('field', False) and views['field'].get('fields', False)
1331 if node.get('name'):
1334 if node.get('name') in self._columns:
1335 column = self._columns[node.get('name')]
1337 column = self._inherit_fields[node.get('name')][2]
1342 relation = self.pool.get(column._obj)
1347 if f.tag in ('form', 'tree', 'graph'):
1349 ctx = context.copy()
1350 ctx['base_model_name'] = self._name
1351 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1352 views[str(f.tag)] = {
1356 attrs = {'views': views}
1357 if node.get('widget') and node.get('widget') == 'selection':
1358 # Prepare the cached selection list for the client. This needs to be
1359 # done even when the field is invisible to the current user, because
1360 # other events could need to change its value to any of the selectable ones
1361 # (such as on_change events, refreshes, etc.)
1363 # If domain and context are strings, we keep them for client-side, otherwise
1364 # we evaluate them server-side to consider them when generating the list of
1366 # TODO: find a way to remove this hack, by allow dynamic domains
1368 if column._domain and not isinstance(column._domain, basestring):
1369 dom = column._domain
1370 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1371 search_context = dict(context)
1372 if column._context and not isinstance(column._context, basestring):
1373 search_context.update(column._context)
1374 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1375 if (node.get('required') and not int(node.get('required'))) or not column.required:
1376 attrs['selection'].append((False, ''))
1377 fields[node.get('name')] = attrs
1379 elif node.tag in ('form', 'tree'):
1380 result = self.view_header_get(cr, user, False, node.tag, context)
1382 node.set('string', result)
1384 elif node.tag == 'calendar':
1385 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1386 if node.get(additional_field):
1387 fields[node.get(additional_field)] = {}
1389 if 'groups' in node.attrib:
1393 if ('lang' in context) and not result:
1394 if node.get('string'):
1395 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1396 if not trans and ('base_model_name' in context):
1397 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1399 node.set('string', trans)
1401 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1403 node.set('sum', trans)
1406 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1407 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1411 def _disable_workflow_buttons(self, cr, user, node):
1413 # admin user can always activate workflow buttons
1416 # TODO handle the case of more than one workflow for a model or multiple
1417 # transitions with different groups and same signal
1418 usersobj = self.pool.get('res.users')
1419 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1420 for button in buttons:
1421 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1422 cr.execute("""SELECT DISTINCT t.group_id
1424 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1425 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1428 AND t.group_id is NOT NULL
1429 """, (self._name, button.get('name')))
1430 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1431 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1432 button.set('readonly', str(int(not can_click)))
1435 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1436 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1437 node = self._disable_workflow_buttons(cr, user, node)
1438 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1440 if node.tag == 'diagram':
1441 if node.getchildren()[0].tag == 'node':
1442 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1443 if node.getchildren()[1].tag == 'arrow':
1444 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1445 for key, value in node_fields.items():
1447 for key, value in arrow_fields.items():
1450 fields = self.fields_get(cr, user, fields_def.keys(), context)
1451 for field in fields_def:
1453 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1454 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1455 elif field in fields:
1456 fields[field].update(fields_def[field])
1458 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))
1459 res = cr.fetchall()[:]
1461 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1462 msg = "\n * ".join([r[0] for r in res])
1463 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1464 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1465 raise except_orm('View error', msg)
1468 def __get_default_calendar_view(self):
1469 """Generate a default calendar view (For internal use only).
1472 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1473 '<calendar string="%s"') % (self._description)
1475 if (self._date_name not in self._columns):
1477 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1478 if dt in self._columns:
1479 self._date_name = dt
1484 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1487 arch += ' date_start="%s"' % (self._date_name)
1489 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1490 if color in self._columns:
1491 arch += ' color="' + color + '"'
1494 dt_stop_flag = False
1496 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1497 if dt_stop in self._columns:
1498 arch += ' date_stop="' + dt_stop + '"'
1502 if not dt_stop_flag:
1503 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1504 if dt_delay in self._columns:
1505 arch += ' date_delay="' + dt_delay + '"'
1509 ' <field name="%s"/>\n'
1510 '</calendar>') % (self._rec_name)
1514 def __get_default_search_view(self, cr, uid, context=None):
1517 if isinstance(s, unicode):
1518 return s.encode('utf8')
1521 view = self.fields_view_get(cr, uid, False, 'form', context=context)
1523 root = etree.fromstring(encode(view['arch']))
1524 res = etree.XML("""<search string="%s"></search>""" % root.get("string", ""))
1525 node = etree.Element("group")
1528 fields = root.xpath("//field[@select=1]")
1529 for field in fields:
1532 return etree.tostring(res, encoding="utf-8").replace('\t', '')
1535 # if view_id, view_type is not required
1537 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1539 Get the detailed composition of the requested view like fields, model, view architecture
1541 :param cr: database cursor
1542 :param user: current user id
1543 :param view_id: id of the view or None
1544 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1545 :param context: context arguments, like lang, time zone
1546 :param toolbar: true to include contextual actions
1547 :param submenu: example (portal_project module)
1548 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1549 :raise AttributeError:
1550 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1551 * if some tag other than 'position' is found in parent view
1552 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1559 if isinstance(s, unicode):
1560 return s.encode('utf8')
1563 def _inherit_apply(src, inherit):
1564 def _find(node, node2):
1565 if node2.tag == 'xpath':
1566 res = node.xpath(node2.get('expr'))
1572 for n in node.getiterator(node2.tag):
1574 if node2.tag == 'field':
1575 # only compare field names, a field can be only once in a given view
1576 # at a given level (and for multilevel expressions, we should use xpath
1577 # inheritance spec anyway)
1578 if node2.get('name') == n.get('name'):
1582 for attr in node2.attrib:
1583 if attr == 'position':
1586 if n.get(attr) == node2.get(attr):
1593 # End: _find(node, node2)
1595 doc_dest = etree.fromstring(encode(inherit))
1596 toparse = [doc_dest]
1599 node2 = toparse.pop(0)
1600 if node2.tag == 'data':
1601 toparse += [ c for c in doc_dest ]
1603 node = _find(src, node2)
1604 if node is not None:
1606 if node2.get('position'):
1607 pos = node2.get('position')
1608 if pos == 'replace':
1609 parent = node.getparent()
1611 src = copy.deepcopy(node2[0])
1614 node.addprevious(child)
1615 node.getparent().remove(node)
1616 elif pos == 'attributes':
1617 for child in node2.getiterator('attribute'):
1618 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1620 node.set(attribute[0], attribute[1])
1622 del(node.attrib[attribute[0]])
1624 sib = node.getnext()
1628 elif pos == 'after':
1632 sib.addprevious(child)
1633 elif pos == 'before':
1634 node.addprevious(child)
1636 raise AttributeError(_('Unknown position in inherited view %s !') % pos)
1639 ' %s="%s"' % (attr, node2.get(attr))
1640 for attr in node2.attrib
1641 if attr != 'position'
1643 tag = "<%s%s>" % (node2.tag, attrs)
1644 raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag)
1646 # End: _inherit_apply(src, inherit)
1648 result = {'type': view_type, 'model': self._name}
1654 view_ref = context.get(view_type + '_view_ref', False)
1655 if view_ref and not view_id:
1657 module, view_ref = view_ref.split('.', 1)
1658 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1659 view_ref_res = cr.fetchone()
1661 view_id = view_ref_res[0]
1664 query = "SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%s"
1667 query += " AND model=%s"
1668 params += (self._name,)
1669 cr.execute(query, params)
1671 cr.execute('''SELECT
1672 arch,name,field_parent,id,type,inherit_id
1679 ORDER BY priority''', (self._name, view_type))
1680 sql_res = cr.fetchone()
1686 view_id = ok or sql_res[3]
1689 # if a view was found
1691 result['type'] = sql_res[4]
1692 result['view_id'] = sql_res[3]
1693 result['arch'] = sql_res[0]
1695 def _inherit_apply_rec(result, inherit_id):
1696 # get all views which inherit from (ie modify) this view
1697 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1698 sql_inherit = cr.fetchall()
1699 for (inherit, id) in sql_inherit:
1700 result = _inherit_apply(result, inherit)
1701 result = _inherit_apply_rec(result, id)
1704 inherit_result = etree.fromstring(encode(result['arch']))
1705 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1707 result['name'] = sql_res[1]
1708 result['field_parent'] = sql_res[2] or False
1711 # otherwise, build some kind of default view
1712 if view_type == 'form':
1713 res = self.fields_get(cr, user, context=context)
1714 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1715 '<form string="%s">' % (self._description,)
1717 if res[x]['type'] not in ('one2many', 'many2many'):
1718 xml += '<field name="%s"/>' % (x,)
1719 if res[x]['type'] == 'text':
1723 elif view_type == 'tree':
1724 _rec_name = self._rec_name
1725 if _rec_name not in self._columns:
1726 _rec_name = self._columns.keys()[0]
1727 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1728 '<tree string="%s"><field name="%s"/></tree>' \
1729 % (self._description, self._rec_name)
1731 elif view_type == 'calendar':
1732 xml = self.__get_default_calendar_view()
1734 elif view_type == 'search':
1735 xml = self.__get_default_search_view(cr, user, context)
1738 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1739 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1740 result['arch'] = etree.fromstring(encode(xml))
1741 result['name'] = 'default'
1742 result['field_parent'] = False
1743 result['view_id'] = 0
1745 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=context)
1746 result['arch'] = xarch
1747 result['fields'] = xfields
1750 if context and context.get('active_id', False):
1751 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1753 act_id = data_menu.id
1755 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1756 result['submenu'] = getattr(data_action, 'menus', False)
1760 for key in ('report_sxw_content', 'report_rml_content',
1761 'report_sxw', 'report_rml',
1762 'report_sxw_content_data', 'report_rml_content_data'):
1766 ir_values_obj = self.pool.get('ir.values')
1767 resprint = ir_values_obj.get(cr, user, 'action',
1768 'client_print_multi', [(self._name, False)], False,
1770 resaction = ir_values_obj.get(cr, user, 'action',
1771 'client_action_multi', [(self._name, False)], False,
1774 resrelate = ir_values_obj.get(cr, user, 'action',
1775 'client_action_relate', [(self._name, False)], False,
1777 resprint = map(clean, resprint)
1778 resaction = map(clean, resaction)
1779 resaction = filter(lambda x: not x.get('multi', False), resaction)
1780 resprint = filter(lambda x: not x.get('multi', False), resprint)
1781 resrelate = map(lambda x: x[2], resrelate)
1783 for x in resprint + resaction + resrelate:
1784 x['string'] = x['name']
1786 result['toolbar'] = {
1788 'action': resaction,
1793 _view_look_dom_arch = __view_look_dom_arch
1795 def search_count(self, cr, user, args, context=None):
1798 res = self.search(cr, user, args, context=context, count=True)
1799 if isinstance(res, list):
1803 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1805 Search for records based on a search domain.
1807 :param cr: database cursor
1808 :param user: current user id
1809 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1810 :param offset: optional number of results to skip in the returned values (default: 0)
1811 :param limit: optional max number of records to return (default: **None**)
1812 :param order: optional columns to sort by (default: self._order=id )
1813 :param context: optional context arguments, like lang, time zone
1814 :type context: dictionary
1815 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1816 :return: id or list of ids of records matching the criteria
1817 :rtype: integer or list of integers
1818 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1820 **Expressing a search domain (args)**
1822 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1824 * **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.
1825 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1826 The semantics of most of these operators are obvious.
1827 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1828 according to the semantics of this model (i.e following the relationship field named by
1829 ``self._parent_name``, by default ``parent_id``.
1830 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1832 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1833 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1834 Be very careful about this when you combine them the first time.
1836 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1838 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1840 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::
1842 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1845 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1847 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1849 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1850 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1851 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1853 :param access_rights_uid: optional user ID to use when checking access rights
1854 (not for ir.rules, this is only for ir.model.access)
1856 raise NotImplementedError(_('The search method is not implemented on this object !'))
1858 def name_get(self, cr, user, ids, context=None):
1861 :param cr: database cursor
1862 :param user: current user id
1864 :param ids: list of ids
1865 :param context: context arguments, like lang, time zone
1866 :type context: dictionary
1867 :return: tuples with the text representation of requested objects for to-many relationships
1874 if isinstance(ids, (int, long)):
1876 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1877 [self._rec_name], context, load='_classic_write')]
1879 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1881 Search for records and their display names according to a search domain.
1883 :param cr: database cursor
1884 :param user: current user id
1885 :param name: object name to search
1886 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1887 :param operator: operator for search criterion
1888 :param context: context arguments, like lang, time zone
1889 :type context: dictionary
1890 :param limit: optional max number of records to return
1891 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1893 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1894 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1897 return self._name_search(cr, user, name, args, operator, context, limit)
1899 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1900 # solve some access rights issues
1901 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1908 args += [(self._rec_name, operator, name)]
1909 access_rights_uid = name_get_uid or user
1910 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1911 res = self.name_get(cr, access_rights_uid, ids, context)
1914 def copy(self, cr, uid, id, default=None, context=None):
1915 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1917 def exists(self, cr, uid, id, context=None):
1918 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1920 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1923 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1925 fields = self._columns.keys() + self._inherit_fields.keys()
1926 #FIXME: collect all calls to _get_source into one SQL call.
1928 res[lang] = {'code': lang}
1930 if f in self._columns:
1931 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1933 res[lang][f] = res_trans
1935 res[lang][f] = self._columns[f].string
1936 for table in self._inherits:
1937 cols = intersect(self._inherit_fields.keys(), fields)
1938 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1941 res[lang]['code'] = lang
1942 for f in res2[lang]:
1943 res[lang][f] = res2[lang][f]
1946 def write_string(self, cr, uid, id, langs, vals, context=None):
1947 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1948 #FIXME: try to only call the translation in one SQL
1951 if field in self._columns:
1952 src = self._columns[field].string
1953 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1954 for table in self._inherits:
1955 cols = intersect(self._inherit_fields.keys(), vals)
1957 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1960 def _check_removed_columns(self, cr, log=False):
1961 raise NotImplementedError()
1963 def _add_missing_default_values(self, cr, uid, values, context=None):
1964 missing_defaults = []
1965 avoid_tables = [] # avoid overriding inherited values when parent is set
1966 for tables, parent_field in self._inherits.items():
1967 if parent_field in values:
1968 avoid_tables.append(tables)
1969 for field in self._columns.keys():
1970 if not field in values:
1971 missing_defaults.append(field)
1972 for field in self._inherit_fields.keys():
1973 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1974 missing_defaults.append(field)
1976 if len(missing_defaults):
1977 # override defaults with the provided values, never allow the other way around
1978 defaults = self.default_get(cr, uid, missing_defaults, context)
1980 if (dv in self._columns and self._columns[dv]._type == 'many2many') \
1981 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many') \
1982 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1983 defaults[dv] = [(6, 0, defaults[dv])]
1984 if dv in self._columns and self._columns[dv]._type == 'one2many' \
1985 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many') \
1986 and isinstance(defaults[dv], (list, tuple)) and isinstance(defaults[dv][0], dict):
1987 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
1988 defaults.update(values)
1992 class orm_memory(orm_template):
1994 _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']
1995 _inherit_fields = {}
2000 def __init__(self, cr):
2001 super(orm_memory, self).__init__(cr)
2005 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2007 def _check_access(self, uid, object_id, mode):
2008 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2009 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2011 def vaccum(self, cr, uid):
2013 if self.check_id % self._check_time:
2016 max = time.time() - self._max_hours * 60 * 60
2017 for id in self.datas:
2018 if self.datas[id]['internal.date_access'] < max:
2020 self.unlink(cr, 1, tounlink)
2021 if len(self.datas) > self._max_count:
2022 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
2024 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
2025 self.unlink(cr, uid, ids)
2028 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2031 if not fields_to_read:
2032 fields_to_read = self._columns.keys()
2036 if isinstance(ids, (int, long)):
2040 for f in fields_to_read:
2041 record = self.datas.get(id)
2043 self._check_access(user, id, 'read')
2044 r[f] = record.get(f, False)
2045 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2048 if id in self.datas:
2049 self.datas[id]['internal.date_access'] = time.time()
2050 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2051 for f in fields_post:
2052 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2053 for record in result:
2054 record[f] = res2[record['id']]
2055 if isinstance(ids_orig, (int, long)):
2059 def write(self, cr, user, ids, vals, context=None):
2065 if self._columns[field]._classic_write:
2066 vals2[field] = vals[field]
2068 upd_todo.append(field)
2069 for object_id in ids:
2070 self._check_access(user, object_id, mode='write')
2071 self.datas[object_id].update(vals2)
2072 self.datas[object_id]['internal.date_access'] = time.time()
2073 for field in upd_todo:
2074 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2075 self._validate(cr, user, [object_id], context)
2076 wf_service = netsvc.LocalService("workflow")
2077 wf_service.trg_write(user, self._name, object_id, cr)
2080 def create(self, cr, user, vals, context=None):
2081 self.vaccum(cr, user)
2083 id_new = self.next_id
2085 vals = self._add_missing_default_values(cr, user, vals, context)
2090 if self._columns[field]._classic_write:
2091 vals2[field] = vals[field]
2093 upd_todo.append(field)
2094 self.datas[id_new] = vals2
2095 self.datas[id_new]['internal.date_access'] = time.time()
2096 self.datas[id_new]['internal.create_uid'] = user
2098 for field in upd_todo:
2099 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2100 self._validate(cr, user, [id_new], context)
2101 if self._log_create and not (context and context.get('no_store_function', False)):
2102 message = self._description + \
2104 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2106 self.log(cr, user, id_new, message, True, context=context)
2107 wf_service = netsvc.LocalService("workflow")
2108 wf_service.trg_create(user, self._name, id_new, cr)
2111 def _where_calc(self, cr, user, args, active_test=True, context=None):
2116 # if the object has a field named 'active', filter out all inactive
2117 # records unless they were explicitely asked for
2118 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2120 active_in_args = False
2122 if a[0] == 'active':
2123 active_in_args = True
2124 if not active_in_args:
2125 args.insert(0, ('active', '=', 1))
2127 args = [('active', '=', 1)]
2130 e = expression.expression(args)
2131 e.parse(cr, user, self, context)
2135 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2139 # implicit filter on current user except for superuser
2143 args.insert(0, ('internal.create_uid', '=', user))
2145 result = self._where_calc(cr, user, args, context=context)
2147 return self.datas.keys()
2151 #Find the value of dict
2154 for id, data in self.datas.items():
2155 counter = counter + 1
2157 if limit and (counter > int(limit)):
2162 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2163 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2164 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2165 elif arg[1] in ['ilike']:
2166 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2176 def unlink(self, cr, uid, ids, context=None):
2178 self._check_access(uid, id, 'unlink')
2179 self.datas.pop(id, None)
2181 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2184 def perm_read(self, cr, user, ids, context=None, details=True):
2186 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2187 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2189 self._check_access(user, id, 'read')
2191 'create_uid': credentials,
2192 'create_date': create_date,
2194 'write_date': False,
2200 def _check_removed_columns(self, cr, log=False):
2201 # nothing to check in memory...
2204 def exists(self, cr, uid, id, context=None):
2205 return id in self.datas
2207 class orm(orm_template):
2208 _sql_constraints = []
2210 _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']
2211 __logger = logging.getLogger('orm')
2212 __schema = logging.getLogger('orm.schema')
2213 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
2215 Get the list of records in list view grouped by the given ``groupby`` fields
2217 :param cr: database cursor
2218 :param uid: current user id
2219 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2220 :param fields: list of fields present in the list view specified on the object
2221 :param groupby: list of fields on which to groupby the records
2222 :type fields_list: list (example ['field_name_1', ...])
2223 :param offset: optional number of records to skip
2224 :param limit: optional max number of records to return
2225 :param context: context arguments, like lang, time zone
2226 :return: list of dictionaries(one dictionary for each record) containing:
2228 * the values of fields grouped by the fields in ``groupby`` argument
2229 * __domain: list of tuples specifying the search criteria
2230 * __context: dictionary with argument like ``groupby``
2231 :rtype: [{'field_name_1': value, ...]
2232 :raise AccessError: * if user has no read rights on the requested object
2233 * if user tries to bypass access rules for read on the requested object
2236 context = context or {}
2237 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2239 fields = self._columns.keys()
2241 query = self._where_calc(cr, uid, domain, context=context)
2242 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2244 # Take care of adding join(s) if groupby is an '_inherits'ed field
2245 groupby_list = groupby
2247 if isinstance(groupby, list):
2248 groupby = groupby[0]
2249 self._inherits_join_calc(groupby, query)
2251 assert not groupby or groupby in fields, "Fields in 'groupby' must appear in the list of fields to read (perhaps it's missing in the list view?)"
2253 fget = self.fields_get(cr, uid, fields)
2254 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2258 if fget.get(groupby):
2259 if fget[groupby]['type'] in ('date', 'datetime'):
2260 flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
2261 groupby = "to_char(%s,'yyyy-mm')" % (groupby)
2265 # Don't allow arbitrary values, as this would be a SQL injection vector!
2266 raise except_orm(_('Invalid group_by'),
2267 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2270 fields_pre = [f for f in float_int_fields if
2271 f == self.CONCURRENCY_CHECK_FIELD
2272 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2273 for f in fields_pre:
2274 if f not in ['id', 'sequence']:
2275 group_operator = fget[f].get('group_operator', 'sum')
2278 flist += group_operator+'('+f+') as '+f
2280 gb = groupby and (' GROUP BY '+groupby) or ''
2282 from_clause, where_clause, where_clause_params = query.get_sql()
2283 where_clause = where_clause and ' WHERE ' + where_clause
2284 limit_str = limit and ' limit %d' % limit or ''
2285 offset_str = offset and ' offset %d' % offset or ''
2286 cr.execute('SELECT min(%s.id) AS id,' % self._table + flist + ' FROM ' + from_clause + where_clause + gb + limit_str + offset_str, where_clause_params)
2289 for r in cr.dictfetchall():
2290 for fld, val in r.items():
2291 if val == None: r[fld] = False
2292 alldata[r['id']] = r
2294 if groupby and fget[groupby]['type'] == 'many2one':
2295 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=groupby, context=context)
2296 # the IDS of the records that has groupby field value = False or ''
2297 # should be added too
2298 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2299 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2300 # restore order of the search as read() uses the default _order (this is only for groups, so the size of data_read shoud be small):
2301 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2303 data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
2305 data.sort(lambda x,y:cmp(x[groupby],y[groupby]))
2308 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2309 if not isinstance(groupby_list, (str, unicode)):
2310 if groupby or not context.get('group_by_no_leaf', False):
2311 d['__context'] = {'group_by': groupby_list[1:]}
2312 if groupby and groupby in fget:
2313 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2314 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2315 days = calendar.monthrange(dt.year, dt.month)[1]
2317 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2318 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),\
2319 (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
2320 del alldata[d['id']][groupby]
2321 d.update(alldata[d['id']])
2325 def _inherits_join_add(self, parent_model_name, query):
2327 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2329 :param parent_model_name: name of the parent model for which the clauses should be added
2330 :param query: query object on which the JOIN should be added
2332 inherits_field = self._inherits[parent_model_name]
2333 parent_model = self.pool.get(parent_model_name)
2334 parent_table_name = parent_model._table
2335 quoted_parent_table_name = '"%s"' % parent_table_name
2336 if quoted_parent_table_name not in query.tables:
2337 query.tables.append(quoted_parent_table_name)
2338 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2340 def _inherits_join_calc(self, field, query):
2342 Adds missing table select and join clause(s) to ``query`` for reaching
2343 the field coming from an '_inherits' parent table (no duplicates).
2345 :param field: name of inherited field to reach
2346 :param query: query object on which the JOIN should be added
2347 :return: qualified name of field, to be used in SELECT clause
2349 current_table = self
2350 while field in current_table._inherit_fields and not field in current_table._columns:
2351 parent_model_name = current_table._inherit_fields[field][0]
2352 parent_table = self.pool.get(parent_model_name)
2353 self._inherits_join_add(parent_model_name, query)
2354 current_table = parent_table
2355 return '"%s".%s' % (current_table._table, field)
2357 def _parent_store_compute(self, cr):
2358 if not self._parent_store:
2360 logger = netsvc.Logger()
2361 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2362 def browse_rec(root, pos=0):
2364 where = self._parent_name+'='+str(root)
2366 where = self._parent_name+' IS NULL'
2367 if self._parent_order:
2368 where += ' order by '+self._parent_order
2369 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2371 for id in cr.fetchall():
2372 pos2 = browse_rec(id[0], pos2)
2373 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2375 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2376 if self._parent_order:
2377 query += ' order by ' + self._parent_order
2380 for (root,) in cr.fetchall():
2381 pos = browse_rec(root, pos)
2384 def _update_store(self, cr, f, k):
2385 logger = netsvc.Logger()
2386 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2387 ss = self._columns[k]._symbol_set
2388 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2389 cr.execute('select id from '+self._table)
2390 ids_lst = map(lambda x: x[0], cr.fetchall())
2393 ids_lst = ids_lst[40:]
2394 res = f.get(cr, self, iids, k, 1, {})
2395 for key, val in res.items():
2398 # if val is a many2one, just write the ID
2399 if type(val) == tuple:
2401 if (val<>False) or (type(val)<>bool):
2402 cr.execute(update_query, (ss[1](val), key))
2404 def _check_removed_columns(self, cr, log=False):
2405 # iterate on the database columns to drop the NOT NULL constraints
2406 # of fields which were required but have been removed (or will be added by another module)
2407 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2408 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2409 cr.execute("SELECT a.attname, a.attnotnull"
2410 " FROM pg_class c, pg_attribute a"
2411 " WHERE c.relname=%s"
2412 " AND c.oid=a.attrelid"
2413 " AND a.attisdropped=%s"
2414 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2415 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2417 for column in cr.dictfetchall():
2419 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2420 column['attname'], self._table, self._name)
2421 if column['attnotnull']:
2422 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2423 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2424 self._table, column['attname'])
2426 def _auto_init(self, cr, context=None):
2429 store_compute = False
2432 self._field_create(cr, context=context)
2433 if getattr(self, '_auto', True):
2434 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2436 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2437 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2439 self.__schema.debug("Table '%s': created", self._table)
2442 if self._parent_store:
2443 cr.execute("""SELECT c.relname
2444 FROM pg_class c, pg_attribute a
2445 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2446 """, (self._table, 'parent_left'))
2448 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2449 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2450 if 'parent_left' not in self._columns:
2451 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2453 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2454 self._table, 'parent_left', 'INTEGER')
2455 if 'parent_right' not in self._columns:
2456 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2458 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2459 self._table, 'parent_right', 'INTEGER')
2460 if self._columns[self._parent_name].ondelete != 'cascade':
2461 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2462 self._parent_name, self._name)
2465 store_compute = True
2467 if self._log_access:
2469 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2470 'create_date': 'TIMESTAMP',
2471 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2472 'write_date': 'TIMESTAMP'
2477 FROM pg_class c, pg_attribute a
2478 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2479 """, (self._table, k))
2481 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2483 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2484 self._table, k, logs[k])
2486 self._check_removed_columns(cr, log=False)
2488 # iterate on the "object columns"
2489 todo_update_store = []
2490 update_custom_fields = context.get('update_custom_fields', False)
2492 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 " \
2493 "FROM pg_class c,pg_attribute a,pg_type t " \
2494 "WHERE c.relname=%s " \
2495 "AND c.oid=a.attrelid " \
2496 "AND a.atttypid=t.oid", (self._table,))
2497 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2500 for k in self._columns:
2501 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2503 #Not Updating Custom fields
2504 if k.startswith('x_') and not update_custom_fields:
2507 f = self._columns[k]
2509 if isinstance(f, fields.one2many):
2510 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2512 if self.pool.get(f._obj):
2513 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2514 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2515 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2518 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))
2519 res = cr.fetchone()[0]
2521 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2522 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2523 self._obj, f._fields_id, f._table)
2524 elif isinstance(f, fields.many2many):
2525 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2526 if not cr.dictfetchall():
2527 if not self.pool.get(f._obj):
2528 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2529 ref = self.pool.get(f._obj)._table
2530 # ref = f._obj.replace('.', '_')
2531 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))
2532 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2533 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2534 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2536 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2537 f._rel, self._table, ref)
2539 res = col_data.get(k, [])
2540 res = res and [res] or []
2541 if not res and hasattr(f, 'oldname'):
2542 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 " \
2543 "FROM pg_class c,pg_attribute a,pg_type t " \
2544 "WHERE c.relname=%s " \
2545 "AND a.attname=%s " \
2546 "AND c.oid=a.attrelid " \
2547 "AND a.atttypid=t.oid", (self._table, f.oldname))
2548 res_old = cr.dictfetchall()
2549 if res_old and len(res_old) == 1:
2550 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2552 res[0]['attname'] = k
2553 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2554 self._table, f.oldname, k)
2558 f_pg_type = f_pg_def['typname']
2559 f_pg_size = f_pg_def['size']
2560 f_pg_notnull = f_pg_def['attnotnull']
2561 if isinstance(f, fields.function) and not f.store and\
2562 not getattr(f, 'nodrop', False):
2563 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2564 k, f.string, self._table)
2565 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2567 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2571 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2576 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2577 ('varchar', 'text', 'TEXT', ''),
2578 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2579 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2580 ('timestamp', 'date', 'date', '::date'),
2581 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2582 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2584 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2585 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2586 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2587 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2588 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2590 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2591 self._table, k, f_pg_size, f.size)
2593 if (f_pg_type==c[0]) and (f._type==c[1]):
2594 if f_pg_type != f_obj_type:
2596 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2597 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2598 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2599 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2601 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2602 self._table, k, c[0], c[1])
2605 if f_pg_type != f_obj_type:
2609 newname = k + '_moved' + str(i)
2610 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2611 "WHERE c.relname=%s " \
2612 "AND a.attname=%s " \
2613 "AND c.oid=a.attrelid ", (self._table, newname))
2614 if not cr.fetchone()[0]:
2618 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2619 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2620 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2621 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2622 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2623 self._table, k, f_pg_type, f._type, newname)
2625 # if the field is required and hasn't got a NOT NULL constraint
2626 if f.required and f_pg_notnull == 0:
2627 # set the field to the default value if any
2628 if k in self._defaults:
2629 if callable(self._defaults[k]):
2630 default = self._defaults[k](self, cr, 1, context)
2632 default = self._defaults[k]
2634 if (default is not None):
2635 ss = self._columns[k]._symbol_set
2636 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2637 cr.execute(query, (ss[1](default),))
2638 # add the NOT NULL constraint
2641 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2643 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2646 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2647 "If you want to have it, you should update the records and execute manually:\n"\
2648 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2649 self.__schema.warn(msg, self._table, k, self._table, k)
2651 elif not f.required and f_pg_notnull == 1:
2652 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2654 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2657 indexname = '%s_%s_index' % (self._table, k)
2658 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2659 res2 = cr.dictfetchall()
2660 if not res2 and f.select:
2661 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2663 if f._type == 'text':
2664 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2665 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2666 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2667 " because there is a length limit for indexable btree values!\n"\
2668 "Use a search view instead if you simply want to make the field searchable."
2669 self.__schema.warn(msg, self._table, k, f._type)
2670 if res2 and not f.select:
2671 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2673 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2674 self.__schema.warn(msg, self._table, k, f._type)
2676 if isinstance(f, fields.many2one):
2677 ref = self.pool.get(f._obj)._table
2678 if ref != 'ir_actions':
2679 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2680 'pg_attribute as att1, pg_attribute as att2 '
2681 'WHERE con.conrelid = cl1.oid '
2682 'AND cl1.relname = %s '
2683 'AND con.confrelid = cl2.oid '
2684 'AND cl2.relname = %s '
2685 'AND array_lower(con.conkey, 1) = 1 '
2686 'AND con.conkey[1] = att1.attnum '
2687 'AND att1.attrelid = cl1.oid '
2688 'AND att1.attname = %s '
2689 'AND array_lower(con.confkey, 1) = 1 '
2690 'AND con.confkey[1] = att2.attnum '
2691 'AND att2.attrelid = cl2.oid '
2692 'AND att2.attname = %s '
2693 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2694 res2 = cr.dictfetchall()
2696 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2697 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2698 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2700 self.__schema.debug("Table '%s': column '%s': XXX",
2703 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2705 if not isinstance(f, fields.function) or f.store:
2706 # add the missing field
2707 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2708 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2709 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2710 self._table, k, get_pg_type(f)[1])
2713 if not create and k in self._defaults:
2714 if callable(self._defaults[k]):
2715 default = self._defaults[k](self, cr, 1, context)
2717 default = self._defaults[k]
2719 ss = self._columns[k]._symbol_set
2720 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2721 cr.execute(query, (ss[1](default),))
2723 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2725 if isinstance(f, fields.function):
2727 if f.store is not True:
2728 order = f.store[f.store.keys()[0]][2]
2729 todo_update_store.append((order, f, k))
2731 # and add constraints if needed
2732 if isinstance(f, fields.many2one):
2733 if not self.pool.get(f._obj):
2734 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2735 ref = self.pool.get(f._obj)._table
2736 # ref = f._obj.replace('.', '_')
2737 # ir_actions is inherited so foreign key doesn't work on it
2738 if ref != 'ir_actions':
2739 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2740 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2741 self._table, k, ref, f.ondelete)
2743 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2747 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2748 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2751 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2752 "Try to re-run: openerp-server.py --update=module\n"\
2753 "If it doesn't work, update records and execute manually:\n"\
2754 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2755 self.__logger.warn(msg, k, self._table, self._table, k)
2757 for order, f, k in todo_update_store:
2758 todo_end.append((order, self._update_store, (f, k)))
2761 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2762 create = not bool(cr.fetchone())
2764 cr.commit() # start a new transaction
2766 for (key, con, _) in self._sql_constraints:
2767 conname = '%s_%s' % (self._table, key)
2769 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2770 existing_constraints = cr.dictfetchall()
2775 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2776 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2777 self._table, conname, con),
2778 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2783 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2784 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2785 'msg_err': "Table '%s': unable to add \'%s\' constraint !\n If you want to have it, you should update the records and execute manually:\n%%s" % (
2791 if not existing_constraints:
2792 # constraint does not exists:
2793 sql_actions['add']['execute'] = True
2794 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2795 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2796 # constraint exists but its definition has changed:
2797 sql_actions['drop']['execute'] = True
2798 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2799 sql_actions['add']['execute'] = True
2800 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2802 # we need to add the constraint:
2803 sql_actions = [item for item in sql_actions.values()]
2804 sql_actions.sort(key=lambda x: x['order'])
2805 for sql_action in [action for action in sql_actions if action['execute']]:
2807 cr.execute(sql_action['query'])
2809 self.__schema.debug(sql_action['msg_ok'])
2811 self.__schema.warn(sql_action['msg_err'])
2815 if hasattr(self, "_sql"):
2816 for line in self._sql.split(';'):
2817 line2 = line.replace('\n', '').strip()
2822 self._parent_store_compute(cr)
2826 def __init__(self, cr):
2827 super(orm, self).__init__(cr)
2829 if not hasattr(self, '_log_access'):
2830 # if not access is not specify, it is the same value as _auto
2831 self._log_access = getattr(self, "_auto", True)
2833 self._columns = self._columns.copy()
2834 for store_field in self._columns:
2835 f = self._columns[store_field]
2836 if hasattr(f, 'digits_change'):
2838 if not isinstance(f, fields.function):
2842 if self._columns[store_field].store is True:
2843 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2845 sm = self._columns[store_field].store
2846 for object, aa in sm.items():
2848 (fnct, fields2, order, length) = aa
2850 (fnct, fields2, order) = aa
2853 raise except_orm('Error',
2854 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2855 self.pool._store_function.setdefault(object, [])
2857 for x, y, z, e, f, l in self.pool._store_function[object]:
2858 if (x==self._name) and (y==store_field) and (e==fields2):
2862 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2863 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2865 for (key, _, msg) in self._sql_constraints:
2866 self.pool._sql_error[self._table+'_'+key] = msg
2868 # Load manual fields
2870 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2872 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2873 for field in cr.dictfetchall():
2874 if field['name'] in self._columns:
2877 'string': field['field_description'],
2878 'required': bool(field['required']),
2879 'readonly': bool(field['readonly']),
2880 'domain': field['domain'] or None,
2881 'size': field['size'],
2882 'ondelete': field['on_delete'],
2883 'translate': (field['translate']),
2884 #'select': int(field['select_level'])
2887 if field['ttype'] == 'selection':
2888 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2889 elif field['ttype'] == 'reference':
2890 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2891 elif field['ttype'] == 'many2one':
2892 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2893 elif field['ttype'] == 'one2many':
2894 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2895 elif field['ttype'] == 'many2many':
2896 _rel1 = field['relation'].replace('.', '_')
2897 _rel2 = field['model'].replace('.', '_')
2898 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2899 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2901 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2902 self._inherits_check()
2903 self._inherits_reload()
2904 if not self._sequence:
2905 self._sequence = self._table + '_id_seq'
2906 for k in self._defaults:
2907 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,)
2908 for f in self._columns:
2909 self._columns[f].restart()
2912 # Update objects that uses this one to update their _inherits fields
2915 def _inherits_reload_src(self):
2916 for obj in self.pool.obj_pool.values():
2917 if self._name in obj._inherits:
2918 obj._inherits_reload()
2920 def _inherits_reload(self):
2922 for table in self._inherits:
2923 res.update(self.pool.get(table)._inherit_fields)
2924 for col in self.pool.get(table)._columns.keys():
2925 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2926 for col in self.pool.get(table)._inherit_fields.keys():
2927 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2928 self._inherit_fields = res
2929 self._inherits_reload_src()
2931 def _inherits_check(self):
2932 for table, field_name in self._inherits.items():
2933 if field_name not in self._columns:
2934 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2935 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2936 required=True, ondelete="cascade")
2937 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2938 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))
2939 self._columns[field_name].required = True
2940 self._columns[field_name].ondelete = "cascade"
2942 #def __getattr__(self, name):
2944 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2945 # (though inherits doesn't use Python inheritance).
2946 # Handles translating between local ids and remote ids.
2947 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2948 # when you have inherits.
2950 # for model, field in self._inherits.iteritems():
2951 # proxy = self.pool.get(model)
2952 # if hasattr(proxy, name):
2953 # attribute = getattr(proxy, name)
2954 # if not hasattr(attribute, '__call__'):
2958 # return super(orm, self).__getattr__(name)
2960 # def _proxy(cr, uid, ids, *args, **kwargs):
2961 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2962 # lst = [obj[field].id for obj in objects if obj[field]]
2963 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2968 def fields_get(self, cr, user, fields=None, context=None):
2970 Get the description of list of fields
2972 :param cr: database cursor
2973 :param user: current user id
2974 :param fields: list of fields
2975 :param context: context arguments, like lang, time zone
2976 :return: dictionary of field dictionaries, each one describing a field of the business object
2977 :raise AccessError: * if user has no create/write rights on the requested object
2980 ira = self.pool.get('ir.model.access')
2981 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2982 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2983 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2985 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2988 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2990 fields = self._columns.keys() + self._inherit_fields.keys()
2991 if isinstance(ids, (int, long)):
2995 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
2996 result = self._read_flat(cr, user, select, fields, context, load)
2999 for key, v in r.items():
3003 if isinstance(ids, (int, long, dict)):
3004 return result and result[0] or False
3007 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
3012 if fields_to_read == None:
3013 fields_to_read = self._columns.keys()
3015 # Construct a clause for the security rules.
3016 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3017 # or will at least contain self._table.
3018 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3020 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
3021 fields_pre = [f for f in fields_to_read if
3022 f == self.CONCURRENCY_CHECK_FIELD
3023 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3024 ] + self._inherits.values()
3028 def convert_field(f):
3029 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
3030 if f in ('create_date', 'write_date'):
3031 return "date_trunc('second', %s) as %s" % (f_qual, f)
3032 if f == self.CONCURRENCY_CHECK_FIELD:
3033 if self._log_access:
3034 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
3035 return "now()::timestamp AS %s" % (f,)
3036 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
3037 return 'length(%s) as "%s"' % (f_qual, f)
3040 fields_pre2 = map(convert_field, fields_pre)
3041 order_by = self._parent_order or self._order
3042 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
3043 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3045 query += " AND " + (' OR '.join(rule_clause))
3046 query += " ORDER BY " + order_by
3047 for sub_ids in cr.split_for_in_conditions(ids):
3049 cr.execute(query, [tuple(sub_ids)] + rule_params)
3050 if cr.rowcount != len(sub_ids):
3051 raise except_orm(_('AccessError'),
3052 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3053 % (self._description,))
3055 cr.execute(query, (tuple(sub_ids),))
3056 res.extend(cr.dictfetchall())
3058 res = map(lambda x: {'id': x}, ids)
3060 for f in fields_pre:
3061 if f == self.CONCURRENCY_CHECK_FIELD:
3063 if self._columns[f].translate:
3064 ids = [x['id'] for x in res]
3065 #TODO: optimize out of this loop
3066 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3068 r[f] = res_trans.get(r['id'], False) or r[f]
3070 for table in self._inherits:
3071 col = self._inherits[table]
3072 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
3075 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3083 if not record[col]: # if the record is deleted from _inherits table?
3085 record.update(res3[record[col]])
3086 if col not in fields_to_read:
3089 # all fields which need to be post-processed by a simple function (symbol_get)
3090 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3093 for f in fields_post:
3094 r[f] = self._columns[f]._symbol_get(r[f])
3095 ids = [x['id'] for x in res]
3097 # all non inherited fields for which the attribute whose name is in load is False
3098 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3100 # Compute POST fields
3102 for f in fields_post:
3103 todo.setdefault(self._columns[f]._multi, [])
3104 todo[self._columns[f]._multi].append(f)
3105 for key, val in todo.items():
3107 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3110 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3111 multi_fields = res2.get(record['id'],{})
3113 record[pos] = multi_fields.get(pos,[])
3116 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3119 record[f] = res2[record['id']]
3124 for field in vals.copy():
3126 if field in self._columns:
3127 fobj = self._columns[field]
3134 for group in groups:
3135 module = group.split(".")[0]
3136 grp = group.split(".")[1]
3137 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" \
3138 (grp, module, 'res.groups', user))
3139 readonly = cr.fetchall()
3140 if readonly[0][0] >= 1:
3143 elif readonly[0][0] == 0:
3149 if type(vals[field]) == type([]):
3151 elif type(vals[field]) == type(0.0):
3153 elif type(vals[field]) == type(''):
3154 vals[field] = '=No Permission='
3159 def perm_read(self, cr, user, ids, context=None, details=True):
3161 Returns some metadata about the given records.
3163 :param details: if True, \*_uid fields are replaced with the name of the user
3164 :return: list of ownership dictionaries for each requested record
3165 :rtype: list of dictionaries with the following keys:
3168 * create_uid: user who created the record
3169 * create_date: date when the record was created
3170 * write_uid: last user who changed the record
3171 * write_date: date of the last change to the record
3172 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3179 uniq = isinstance(ids, (int, long))
3183 if self._log_access:
3184 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3185 quoted_table = '"%s"' % self._table
3186 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3187 query = '''SELECT %s, __imd.module, __imd.name
3188 FROM %s LEFT JOIN ir_model_data __imd
3189 ON (__imd.model = %%s and __imd.res_id = %s.id)
3190 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3191 cr.execute(query, (self._name, tuple(ids)))
3192 res = cr.dictfetchall()
3195 r[key] = r[key] or False
3196 if details and key in ('write_uid', 'create_uid') and r[key]:
3198 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3200 pass # Leave the numeric uid there
3201 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3202 del r['name'], r['module']
3207 def _check_concurrency(self, cr, ids, context):
3210 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3212 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3213 for sub_ids in cr.split_for_in_conditions(ids):
3216 id_ref = "%s,%s" % (self._name, id)
3217 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3219 ids_to_check.extend([id, update_date])
3220 if not ids_to_check:
3222 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3225 # mention the first one only to keep the error message readable
3226 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3228 def check_access_rule(self, cr, uid, ids, operation, context=None):
3229 """Verifies that the operation given by ``operation`` is allowed for the user
3230 according to ir.rules.
3232 :param operation: one of ``write``, ``unlink``
3233 :raise except_orm: * if current ir.rules do not permit this operation.
3234 :return: None if the operation is allowed
3236 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3238 where_clause = ' and ' + ' and '.join(where_clause)
3239 for sub_ids in cr.split_for_in_conditions(ids):
3240 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3241 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3242 [sub_ids] + where_params)
3243 if cr.rowcount != len(sub_ids):
3244 raise except_orm(_('AccessError'),
3245 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3246 % (operation, self._description))
3248 def unlink(self, cr, uid, ids, context=None):
3250 Delete records with given ids
3252 :param cr: database cursor
3253 :param uid: current user id
3254 :param ids: id or list of ids
3255 :param context: (optional) context arguments, like lang, time zone
3257 :raise AccessError: * if user has no unlink rights on the requested object
3258 * if user tries to bypass access rules for unlink on the requested object
3259 :raise UserError: if the record is default property for other records
3264 if isinstance(ids, (int, long)):
3267 result_store = self._store_get_values(cr, uid, ids, None, context)
3269 self._check_concurrency(cr, ids, context)
3271 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3273 properties = self.pool.get('ir.property')
3274 domain = [('res_id', '=', False),
3275 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3277 if properties.search(cr, uid, domain, context=context):
3278 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3280 wf_service = netsvc.LocalService("workflow")
3282 wf_service.trg_delete(uid, self._name, oid, cr)
3285 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3286 for sub_ids in cr.split_for_in_conditions(ids):
3287 cr.execute('delete from ' + self._table + ' ' \
3288 'where id IN %s', (sub_ids,))
3289 for order, object, store_ids, fields in result_store:
3290 if object != self._name:
3291 obj = self.pool.get(object)
3292 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3293 rids = map(lambda x: x[0], cr.fetchall())
3295 obj._store_set_values(cr, uid, rids, fields, context)
3301 def write(self, cr, user, ids, vals, context=None):
3303 Update records with given ids with the given field values
3305 :param cr: database cursor
3306 :param user: current user id
3308 :param ids: object id or list of object ids to update according to **vals**
3309 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3310 :type vals: dictionary
3311 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3312 :type context: dictionary
3314 :raise AccessError: * if user has no write rights on the requested object
3315 * if user tries to bypass access rules for write on the requested object
3316 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3317 :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)
3319 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3321 + For a many2many field, a list of tuples is expected.
3322 Here is the list of tuple that are accepted, with the corresponding semantics ::
3324 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3325 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3326 (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)
3327 (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)
3328 (4, ID) link to existing record with id = ID (adds a relationship)
3329 (5) unlink all (like using (3,ID) for all linked records)
3330 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3333 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3335 + For a one2many field, a lits of tuples is expected.
3336 Here is the list of tuple that are accepted, with the corresponding semantics ::
3338 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3339 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3340 (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)
3343 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3345 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3346 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3350 for field in vals.copy():
3352 if field in self._columns:
3353 fobj = self._columns[field]
3355 fobj = self._inherit_fields[field][2]
3362 for group in groups:
3363 module = group.split(".")[0]
3364 grp = group.split(".")[1]
3365 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", \
3366 (grp, module, 'res.groups', user))
3367 readonly = cr.fetchall()
3368 if readonly[0][0] >= 1:
3371 elif readonly[0][0] == 0:
3383 if isinstance(ids, (int, long)):
3386 self._check_concurrency(cr, ids, context)
3387 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3389 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3391 # No direct update of parent_left/right
3392 vals.pop('parent_left', None)
3393 vals.pop('parent_right', None)
3395 parents_changed = []
3396 if self._parent_store and (self._parent_name in vals):
3397 # The parent_left/right computation may take up to
3398 # 5 seconds. No need to recompute the values if the
3399 # parent is the same. Get the current value of the parent
3400 parent_val = vals[self._parent_name]
3402 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3403 (self._table, self._parent_name, self._parent_name)
3404 cr.execute(query, (tuple(ids), parent_val))
3406 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3407 (self._table, self._parent_name)
3408 cr.execute(query, (tuple(ids),))
3409 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3416 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3418 if field in self._columns:
3419 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3420 if (not totranslate) or not self._columns[field].translate:
3421 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3422 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3423 direct.append(field)
3425 upd_todo.append(field)
3427 updend.append(field)
3428 if field in self._columns \
3429 and hasattr(self._columns[field], 'selection') \
3431 if self._columns[field]._type == 'reference':
3432 val = vals[field].split(',')[0]
3435 if isinstance(self._columns[field].selection, (tuple, list)):
3436 if val not in dict(self._columns[field].selection):
3437 raise except_orm(_('ValidateError'),
3438 _('The value "%s" for the field "%s" is not in the selection') \
3439 % (vals[field], field))
3441 if val not in dict(self._columns[field].selection(
3442 self, cr, user, context=context)):
3443 raise except_orm(_('ValidateError'),
3444 _('The value "%s" for the field "%s" is not in the selection') \
3445 % (vals[field], field))
3447 if self._log_access:
3448 upd0.append('write_uid=%s')
3449 upd0.append('write_date=now()')
3453 self.check_access_rule(cr, user, ids, 'write', context=context)
3454 for sub_ids in cr.split_for_in_conditions(ids):
3455 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3456 'where id IN %s', upd1 + [sub_ids])
3457 if cr.rowcount != len(sub_ids):
3458 raise except_orm(_('AccessError'),
3459 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3464 if self._columns[f].translate:
3465 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3468 # Inserting value to DB
3469 self.write(cr, user, ids, {f: vals[f]})
3470 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3473 # call the 'set' method of fields which are not classic_write
3474 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3476 # default element in context must be removed when call a one2many or many2many
3477 rel_context = context.copy()
3478 for c in context.items():
3479 if c[0].startswith('default_'):
3480 del rel_context[c[0]]
3482 for field in upd_todo:
3484 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3486 for table in self._inherits:
3487 col = self._inherits[table]
3489 for sub_ids in cr.split_for_in_conditions(ids):
3490 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3491 'where id IN %s', (sub_ids,))
3492 nids.extend([x[0] for x in cr.fetchall()])
3496 if self._inherit_fields[val][0] == table:
3499 self.pool.get(table).write(cr, user, nids, v, context)
3501 self._validate(cr, user, ids, context)
3503 # TODO: use _order to set dest at the right position and not first node of parent
3504 # We can't defer parent_store computation because the stored function
3505 # fields that are computer may refer (directly or indirectly) to
3506 # parent_left/right (via a child_of domain)
3509 self.pool._init_parent[self._name] = True
3511 order = self._parent_order or self._order
3512 parent_val = vals[self._parent_name]
3514 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3516 clause, params = '%s IS NULL' % (self._parent_name,), ()
3517 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3518 parents = cr.fetchall()
3520 for id in parents_changed:
3521 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3522 pleft, pright = cr.fetchone()
3523 distance = pright - pleft + 1
3525 # Find Position of the element
3527 for (parent_pright, parent_id) in parents:
3530 position = parent_pright + 1
3532 # It's the first node of the parent
3537 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3538 position = cr.fetchone()[0] + 1
3540 if pleft < position <= pright:
3541 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3543 if pleft < position:
3544 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3545 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3546 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))
3548 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3549 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3550 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))
3552 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3556 for order, object, ids, fields in result:
3557 key = (object, tuple(fields))
3558 done.setdefault(key, {})
3559 # avoid to do several times the same computation
3562 if id not in done[key]:
3563 done[key][id] = True
3565 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3567 wf_service = netsvc.LocalService("workflow")
3569 wf_service.trg_write(user, self._name, id, cr)
3573 # TODO: Should set perm to user.xxx
3575 def create(self, cr, user, vals, context=None):
3577 Create new record with specified value
3579 :param cr: database cursor
3580 :param user: current user id
3582 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3583 :type vals: dictionary
3584 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3585 :type context: dictionary
3586 :return: id of new record created
3587 :raise AccessError: * if user has no create rights on the requested object
3588 * if user tries to bypass access rules for create on the requested object
3589 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3590 :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)
3592 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3593 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3599 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3601 vals = self._add_missing_default_values(cr, user, vals, context)
3604 for v in self._inherits:
3605 if self._inherits[v] not in vals:
3608 tocreate[v] = {'id': vals[self._inherits[v]]}
3609 (upd0, upd1, upd2) = ('', '', [])
3611 for v in vals.keys():
3612 if v in self._inherit_fields:
3613 (table, col, col_detail) = self._inherit_fields[v]
3614 tocreate[table][v] = vals[v]
3617 if (v not in self._inherit_fields) and (v not in self._columns):
3620 # Try-except added to filter the creation of those records whose filds are readonly.
3621 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3623 cr.execute("SELECT nextval('"+self._sequence+"')")
3625 raise except_orm(_('UserError'),
3626 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3628 id_new = cr.fetchone()[0]
3629 for table in tocreate:
3630 if self._inherits[table] in vals:
3631 del vals[self._inherits[table]]
3633 record_id = tocreate[table].pop('id', None)
3635 if record_id is None or not record_id:
3636 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3638 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3640 upd0 += ',' + self._inherits[table]
3642 upd2.append(record_id)
3644 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3645 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3647 for bool_field in bool_fields:
3648 if bool_field not in vals:
3649 vals[bool_field] = False
3651 for field in vals.copy():
3653 if field in self._columns:
3654 fobj = self._columns[field]
3656 fobj = self._inherit_fields[field][2]
3662 for group in groups:
3663 module = group.split(".")[0]
3664 grp = group.split(".")[1]
3665 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" % \
3666 (grp, module, 'res.groups', user))
3667 readonly = cr.fetchall()
3668 if readonly[0][0] >= 1:
3671 elif readonly[0][0] == 0:
3679 if self._columns[field]._classic_write:
3680 upd0 = upd0 + ',"' + field + '"'
3681 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3682 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3684 if not isinstance(self._columns[field], fields.related):
3685 upd_todo.append(field)
3686 if field in self._columns \
3687 and hasattr(self._columns[field], 'selection') \
3689 if self._columns[field]._type == 'reference':
3690 val = vals[field].split(',')[0]
3693 if isinstance(self._columns[field].selection, (tuple, list)):
3694 if val not in dict(self._columns[field].selection):
3695 raise except_orm(_('ValidateError'),
3696 _('The value "%s" for the field "%s" is not in the selection') \
3697 % (vals[field], field))
3699 if val not in dict(self._columns[field].selection(
3700 self, cr, user, context=context)):
3701 raise except_orm(_('ValidateError'),
3702 _('The value "%s" for the field "%s" is not in the selection') \
3703 % (vals[field], field))
3704 if self._log_access:
3705 upd0 += ',create_uid,create_date'
3708 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3709 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3710 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3712 if self._parent_store and not context.get('defer_parent_store_computation'):
3714 self.pool._init_parent[self._name] = True
3716 parent = vals.get(self._parent_name, False)
3718 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3720 result_p = cr.fetchall()
3721 for (pleft,) in result_p:
3726 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3727 pleft_old = cr.fetchone()[0]
3730 cr.execute('select max(parent_right) from '+self._table)
3731 pleft = cr.fetchone()[0] or 0
3732 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3733 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3734 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3736 # default element in context must be remove when call a one2many or many2many
3737 rel_context = context.copy()
3738 for c in context.items():
3739 if c[0].startswith('default_'):
3740 del rel_context[c[0]]
3743 for field in upd_todo:
3744 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3745 self._validate(cr, user, [id_new], context)
3747 if not context.get('no_store_function', False):
3748 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3751 for order, object, ids, fields2 in result:
3752 if not (object, ids, fields2) in done:
3753 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3754 done.append((object, ids, fields2))
3756 if self._log_create and not (context and context.get('no_store_function', False)):
3757 message = self._description + \
3759 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3760 "' " + _("created.")
3761 self.log(cr, user, id_new, message, True, context=context)
3762 wf_service = netsvc.LocalService("workflow")
3763 wf_service.trg_create(user, self._name, id_new, cr)
3766 def _store_get_values(self, cr, uid, ids, fields, context):
3768 fncts = self.pool._store_function.get(self._name, [])
3769 for fnct in range(len(fncts)):
3774 for f in (fields or []):
3775 if f in fncts[fnct][3]:
3781 result.setdefault(fncts[fnct][0], {})
3783 # uid == 1 for accessing objects having rules defined on store fields
3784 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3785 for id in filter(None, ids2):
3786 result[fncts[fnct][0]].setdefault(id, [])
3787 result[fncts[fnct][0]][id].append(fnct)
3789 for object in result:
3791 for id, fnct in result[object].items():
3792 k2.setdefault(tuple(fnct), [])
3793 k2[tuple(fnct)].append(id)
3794 for fnct, id in k2.items():
3795 dict.setdefault(fncts[fnct[0]][4], [])
3796 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3804 def _store_set_values(self, cr, uid, ids, fields, context):
3809 if self._log_access:
3810 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3814 field_dict.setdefault(r[0], [])
3815 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3816 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3817 for i in self.pool._store_function.get(self._name, []):
3819 up_write_date = write_date + datetime.timedelta(hours=i[5])
3820 if datetime.datetime.now() < up_write_date:
3822 field_dict[r[0]].append(i[1])
3828 if self._columns[f]._multi not in keys:
3829 keys.append(self._columns[f]._multi)
3830 todo.setdefault(self._columns[f]._multi, [])
3831 todo[self._columns[f]._multi].append(f)
3835 # uid == 1 for accessing objects having rules defined on store fields
3836 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3837 for id, value in result.items():
3839 for f in value.keys():
3840 if f in field_dict[id]:
3847 if self._columns[v]._type in ('many2one', 'one2one'):
3849 value[v] = value[v][0]
3852 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3853 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3856 cr.execute('update "' + self._table + '" set ' + \
3857 ','.join(upd0) + ' where id = %s', upd1)
3861 # uid == 1 for accessing objects having rules defined on store fields
3862 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3863 for r in result.keys():
3865 if r in field_dict.keys():
3866 if f in field_dict[r]:
3868 for id, value in result.items():
3869 if self._columns[f]._type in ('many2one', 'one2one'):
3874 cr.execute('update "' + self._table + '" set ' + \
3875 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3881 def perm_write(self, cr, user, ids, fields, context=None):
3882 raise NotImplementedError(_('This method does not exist anymore'))
3884 # TODO: ameliorer avec NULL
3885 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3886 """Computes the WHERE clause needed to implement an OpenERP domain.
3887 :param domain: the domain to compute
3889 :param active_test: whether the default filtering of records with ``active``
3890 field set to ``False`` should be applied.
3891 :return: the query expressing the given domain as provided in domain
3892 :rtype: osv.query.Query
3897 # if the object has a field named 'active', filter out all inactive
3898 # records unless they were explicitely asked for
3899 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3901 active_in_args = False
3903 if a[0] == 'active':
3904 active_in_args = True
3905 if not active_in_args:
3906 domain.insert(0, ('active', '=', 1))
3908 domain = [('active', '=', 1)]
3912 e = expression.expression(domain)
3913 e.parse(cr, user, self, context)
3914 tables = e.get_tables()
3915 where_clause, where_params = e.to_sql()
3916 where_clause = where_clause and [where_clause] or []
3918 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3920 return Query(tables, where_clause, where_params)
3922 def _check_qorder(self, word):
3923 if not regex_order.match(word):
3924 raise except_orm(_('AccessError'), _('Invalid "order" specified. A valid "order" specification is a comma-separated list of valid field names (optionally followed by asc/desc for the direction)'))
3927 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3928 """Add what's missing in ``query`` to implement all appropriate ir.rules
3929 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3931 :param query: the current query object
3933 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3935 if parent_model and child_object:
3936 # as inherited rules are being applied, we need to add the missing JOIN
3937 # to reach the parent table (if it was not JOINed yet in the query)
3938 child_object._inherits_join_add(parent_model, query)
3939 query.where_clause += added_clause
3940 query.where_clause_params += added_params
3941 for table in added_tables:
3942 if table not in query.tables:
3943 query.tables.append(table)
3947 # apply main rules on the object
3948 rule_obj = self.pool.get('ir.rule')
3949 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3951 # apply ir.rules from the parents (through _inherits)
3952 for inherited_model in self._inherits:
3953 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3954 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3956 def _generate_m2o_order_by(self, order_field, query):
3958 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3959 either native m2o fields or function/related fields that are stored, including
3960 intermediate JOINs for inheritance if required.
3962 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3964 if order_field not in self._columns and order_field in self._inherit_fields:
3965 # also add missing joins for reaching the table containing the m2o field
3966 qualified_field = self._inherits_join_calc(order_field, query)
3967 order_field_column = self._inherit_fields[order_field][2]
3969 qualified_field = '"%s"."%s"' % (self._table, order_field)
3970 order_field_column = self._columns[order_field]
3972 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3973 assert order_field_column._classic_write or getattr(order_field_column, 'store', False), "Many2one function/related fields must be stored to be used as ordering fields"
3975 # figure out the applicable order_by for the m2o
3976 dest_model = self.pool.get(order_field_column._obj)
3977 m2o_order = dest_model._order
3978 if not regex_order.match(m2o_order):
3979 # _order is complex, can't use it here, so we default to _rec_name
3980 m2o_order = dest_model._rec_name
3982 # extract the first field name, to be able to qualify it and add desc/asc
3983 m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
3985 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3986 # as we don't want to exclude results that have NULL values for the m2o
3987 src_table, src_field = qualified_field.replace('"','').split('.', 1)
3988 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3989 return '"%s"."%s"' % (dest_model._table, m2o_order)
3992 def _generate_order_by(self, order_spec, query):
3994 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
3995 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
3997 :raise" except_orm in case order_spec is malformed
3999 order_by_clause = self._order
4001 order_by_elements = []
4002 self._check_qorder(order_spec)
4003 for order_part in order_spec.split(','):
4004 order_split = order_part.strip().split(' ')
4005 order_field = order_split[0].strip()
4006 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
4007 if order_field in self._columns:
4008 order_column = self._columns[order_field]
4009 if order_column._classic_read:
4010 order_by_clause = '"%s"."%s"' % (self._table, order_field)
4011 elif order_column._type == 'many2one':
4012 order_by_clause = self._generate_m2o_order_by(order_field, query)
4014 continue # ignore non-readable or "non-joignable" fields
4015 elif order_field in self._inherit_fields:
4016 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4017 order_column = parent_obj._columns[order_field]
4018 if order_column._classic_read:
4019 order_by_clause = self._inherits_join_calc(order_field, query)
4020 elif order_column._type == 'many2one':
4021 order_by_clause = self._generate_m2o_order_by(order_field, query)
4023 continue # ignore non-readable or "non-joignable" fields
4024 order_by_elements.append("%s %s" % (order_by_clause, order_direction))
4025 order_by_clause = ",".join(order_by_elements)
4027 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4029 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4031 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4032 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4033 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4034 This is ok at the security level because this method is private and not callable through XML-RPC.
4036 :param access_rights_uid: optional user ID to use when checking access rights
4037 (not for ir.rules, this is only for ir.model.access)
4041 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4043 query = self._where_calc(cr, user, args, context=context)
4044 self._apply_ir_rules(cr, user, query, 'read', context=context)
4045 order_by = self._generate_order_by(order, query)
4046 from_clause, where_clause, where_clause_params = query.get_sql()
4048 limit_str = limit and ' limit %d' % limit or ''
4049 offset_str = offset and ' offset %d' % offset or ''
4050 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4053 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4056 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4058 return [x[0] for x in res]
4060 # returns the different values ever entered for one field
4061 # this is used, for example, in the client when the user hits enter on
4063 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4066 if field in self._inherit_fields:
4067 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4069 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4071 def copy_data(self, cr, uid, id, default=None, context=None):
4073 Copy given record's data with all its fields values
4075 :param cr: database cursor
4076 :param user: current user id
4077 :param id: id of the record to copy
4078 :param default: field values to override in the original values of the copied record
4079 :type default: dictionary
4080 :param context: context arguments, like lang, time zone
4081 :type context: dictionary
4082 :return: dictionary containing all the field values
4089 if 'state' not in default:
4090 if 'state' in self._defaults:
4091 if callable(self._defaults['state']):
4092 default['state'] = self._defaults['state'](self, cr, uid, context)
4094 default['state'] = self._defaults['state']
4096 context_wo_lang = context.copy()
4097 if 'lang' in context:
4098 del context_wo_lang['lang']
4099 data = self.read(cr, uid, [id,], context=context_wo_lang)
4103 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4105 fields = self.fields_get(cr, uid, context=context)
4107 ftype = fields[f]['type']
4109 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4113 data[f] = default[f]
4114 elif ftype == 'function':
4116 elif ftype == 'many2one':
4118 data[f] = data[f] and data[f][0]
4121 elif ftype in ('one2many', 'one2one'):
4123 rel = self.pool.get(fields[f]['relation'])
4125 # duplicate following the order of the ids
4126 # because we'll rely on it later for copying
4127 # translations in copy_translation()!
4129 for rel_id in data[f]:
4130 # the lines are first duplicated using the wrong (old)
4131 # parent but then are reassigned to the correct one thanks
4132 # to the (0, 0, ...)
4133 d = rel.copy_data(cr, uid, rel_id, context=context)
4134 res.append((0, 0, d))
4136 elif ftype == 'many2many':
4137 data[f] = [(6, 0, data[f])]
4141 # make sure we don't break the current parent_store structure and
4142 # force a clean recompute!
4143 for parent_column in ['parent_left', 'parent_right']:
4144 data.pop(parent_column, None)
4146 for v in self._inherits:
4147 del data[self._inherits[v]]
4150 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4151 trans_obj = self.pool.get('ir.translation')
4152 fields = self.fields_get(cr, uid, context=context)
4154 translation_records = []
4155 for field_name, field_def in fields.items():
4156 # we must recursively copy the translations for o2o and o2m
4157 if field_def['type'] in ('one2one', 'one2many'):
4158 target_obj = self.pool.get(field_def['relation'])
4159 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4160 # here we rely on the order of the ids to match the translations
4161 # as foreseen in copy_data()
4162 old_children = sorted(old_record[field_name])
4163 new_children = sorted(new_record[field_name])
4164 for (old_child, new_child) in zip(old_children, new_children):
4165 # recursive copy of translations here
4166 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4167 # and for translatable fields we keep them for copy
4168 elif field_def.get('translate'):
4170 if field_name in self._columns:
4171 trans_name = self._name + "," + field_name
4172 elif field_name in self._inherit_fields:
4173 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4175 trans_ids = trans_obj.search(cr, uid, [
4176 ('name', '=', trans_name),
4177 ('res_id', '=', old_id)
4179 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4181 for record in translation_records:
4183 record['res_id'] = new_id
4184 trans_obj.create(cr, uid, record, context=context)
4187 def copy(self, cr, uid, id, default=None, context=None):
4189 Duplicate record with given id updating it with default values
4191 :param cr: database cursor
4192 :param uid: current user id
4193 :param id: id of the record to copy
4194 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4195 :type default: dictionary
4196 :param context: context arguments, like lang, time zone
4197 :type context: dictionary
4201 data = self.copy_data(cr, uid, id, default, context)
4202 new_id = self.create(cr, uid, data, context)
4203 self.copy_translations(cr, uid, id, new_id, context)
4206 def exists(self, cr, uid, ids, context=None):
4207 if type(ids) in (int, long):
4209 query = 'SELECT count(1) FROM "%s"' % (self._table)
4210 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4211 return cr.fetchone()[0] == len(ids)
4213 def check_recursion(self, cr, uid, ids, parent=None):
4214 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4215 self._name, DeprecationWarning, stacklevel=3)
4216 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4217 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4218 return self._check_recursion(cr, uid, ids, parent)
4220 def _check_recursion(self, cr, uid, ids, parent=None):
4222 Verifies that there is no loop in a hierarchical structure of records,
4223 by following the parent relationship using the **parent** field until a loop
4224 is detected or until a top-level record is found.
4226 :param cr: database cursor
4227 :param uid: current user id
4228 :param ids: list of ids of records to check
4229 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4230 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4234 parent = self._parent_name
4236 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4239 for i in range(0, len(ids), cr.IN_MAX):
4240 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4241 cr.execute(query, (tuple(sub_ids_parent),))
4242 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4243 ids_parent = ids_parent2
4244 for i in ids_parent:
4249 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4250 """Find out the XML ID of any database record, if there
4251 is one. This method works as a possible implementation
4252 for a function field, to be able to add it to any
4253 model object easily, referencing it as ``osv.osv.get_xml_id``.
4255 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4257 :return: the fully qualified XML ID of the given object,
4258 defaulting to an empty string when there's none
4259 (to be usable as a function field).
4261 result = dict.fromkeys(ids, '')
4262 model_data_obj = self.pool.get('ir.model.data')
4263 data_ids = model_data_obj.search(cr, uid,
4264 [('model', '=', self._name), ('res_id', 'in', ids)])
4265 data_results = model_data_obj.read(cr, uid, data_ids,
4266 ['name', 'module', 'res_id'])
4267 for record in data_results:
4268 result[record['res_id']] = '%(module)s.%(name)s' % record
4271 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: