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 # List of etree._Element subclasses that we choose to ignore when parsing XML.
63 from tools import SKIPPED_ELEMENT_TYPES
65 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
67 POSTGRES_CONFDELTYPES = {
75 def last_day_of_current_month():
76 today = datetime.date.today()
77 last_day = str(calendar.monthrange(today.year, today.month)[1])
78 return time.strftime('%Y-%m-' + last_day)
80 def intersect(la, lb):
81 return filter(lambda x: x in lb, la)
83 class except_orm(Exception):
84 def __init__(self, name, value):
87 self.args = (name, value)
89 class BrowseRecordError(Exception):
92 # Readonly python database object browser
93 class browse_null(object):
98 def __getitem__(self, name):
101 def __getattr__(self, name):
102 return None # XXX: return self ?
110 def __nonzero__(self):
113 def __unicode__(self):
118 # TODO: execute an object method on browse_record_list
120 class browse_record_list(list):
122 def __init__(self, lst, context=None):
125 super(browse_record_list, self).__init__(lst)
126 self.context = context
129 class browse_record(object):
130 logger = netsvc.Logger()
132 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
134 table : the object (inherited from orm)
135 context : dictionary with an optional context
137 if fields_process is None:
141 self._list_class = list_class or browse_record_list
146 self._table_name = self._table._name
147 self.__logger = logging.getLogger(
148 'osv.browse_record.' + self._table_name)
149 self._context = context
150 self._fields_process = fields_process
152 cache.setdefault(table._name, {})
153 self._data = cache[table._name]
155 if not (id and isinstance(id, (int, long,))):
156 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
157 # if not table.exists(cr, uid, id, context):
158 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
160 if id not in self._data:
161 self._data[id] = {'id': id}
165 def __getitem__(self, name):
169 if name not in self._data[self._id]:
170 # build the list of fields we will fetch
172 # fetch the definition of the field which was asked for
173 if name in self._table._columns:
174 col = self._table._columns[name]
175 elif name in self._table._inherit_fields:
176 col = self._table._inherit_fields[name][2]
177 elif hasattr(self._table, str(name)):
178 attr = getattr(self._table, name)
180 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
181 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
185 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
186 "Field '%s' does not exist in object '%s': \n%s" % (
187 name, self, ''.join(traceback.format_exc())))
188 raise KeyError("Field '%s' does not exist in object '%s'" % (
191 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
193 # gen the list of "local" (ie not inherited) fields which are classic or many2one
194 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
195 # gen the list of inherited fields
196 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
197 # complete the field list with the inherited fields which are classic or many2one
198 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
199 # otherwise we fetch only that field
201 fields_to_fetch = [(name, col)]
202 ids = filter(lambda id: name not in self._data[id], self._data.keys())
204 field_names = map(lambda x: x[0], fields_to_fetch)
205 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
207 # TODO: improve this, very slow for reports
208 if self._fields_process:
209 lang = self._context.get('lang', 'en_US') or 'en_US'
210 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
212 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
213 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
215 for field_name, field_column in fields_to_fetch:
216 if field_column._type in self._fields_process:
217 for result_line in field_values:
218 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
219 if result_line[field_name]:
220 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
223 # Where did those ids come from? Perhaps old entries in ir_model_dat?
224 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
225 raise KeyError('Field %s not found in %s'%(name, self))
226 # create browse records for 'remote' objects
227 for result_line in field_values:
229 for field_name, field_column in fields_to_fetch:
230 if field_column._type in ('many2one', 'one2one'):
231 if result_line[field_name]:
232 obj = self._table.pool.get(field_column._obj)
233 if isinstance(result_line[field_name], (list, tuple)):
234 value = result_line[field_name][0]
236 value = result_line[field_name]
238 # FIXME: this happen when a _inherits object
239 # overwrite a field of it parent. Need
240 # testing to be sure we got the right
241 # object and not the parent one.
242 if not isinstance(value, browse_record):
244 # In some cases the target model is not available yet, so we must ignore it,
245 # which is safe in most cases, this value will just be loaded later when needed.
246 # This situation can be caused by custom fields that connect objects with m2o without
247 # respecting module dependencies, causing relationships to be connected to soon when
248 # the target is not loaded yet.
250 new_data[field_name] = browse_record(self._cr,
251 self._uid, value, obj, self._cache,
252 context=self._context,
253 list_class=self._list_class,
254 fields_process=self._fields_process)
256 new_data[field_name] = value
258 new_data[field_name] = browse_null()
260 new_data[field_name] = browse_null()
261 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
262 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)
263 elif field_column._type in ('reference'):
264 if result_line[field_name]:
265 if isinstance(result_line[field_name], browse_record):
266 new_data[field_name] = result_line[field_name]
268 ref_obj, ref_id = result_line[field_name].split(',')
269 ref_id = long(ref_id)
271 obj = self._table.pool.get(ref_obj)
272 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)
274 new_data[field_name] = browse_null()
276 new_data[field_name] = browse_null()
278 new_data[field_name] = result_line[field_name]
279 self._data[result_line['id']].update(new_data)
281 if not name in self._data[self._id]:
282 # How did this happen? Could be a missing model due to custom fields used too soon, see above.
283 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
284 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
285 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
286 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
287 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
288 return self._data[self._id][name]
290 def __getattr__(self, name):
294 raise AttributeError(e)
296 def __contains__(self, name):
297 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
299 def __hasattr__(self, name):
306 return "browse_record(%s, %d)" % (self._table_name, self._id)
308 def __eq__(self, other):
309 if not isinstance(other, browse_record):
311 return (self._table_name, self._id) == (other._table_name, other._id)
313 def __ne__(self, other):
314 if not isinstance(other, browse_record):
316 return (self._table_name, self._id) != (other._table_name, other._id)
318 # we need to define __unicode__ even though we've already defined __str__
319 # because we have overridden __getattr__
320 def __unicode__(self):
321 return unicode(str(self))
324 return hash((self._table_name, self._id))
332 (type returned by postgres when the column was created, type expression to create the column)
336 fields.boolean: 'bool',
337 fields.integer: 'int4',
338 fields.integer_big: 'int8',
342 fields.datetime: 'timestamp',
343 fields.binary: 'bytea',
344 fields.many2one: 'int4',
346 if type(f) in type_dict:
347 f_type = (type_dict[type(f)], type_dict[type(f)])
348 elif isinstance(f, fields.float):
350 f_type = ('numeric', 'NUMERIC')
352 f_type = ('float8', 'DOUBLE PRECISION')
353 elif isinstance(f, (fields.char, fields.reference)):
354 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
355 elif isinstance(f, fields.selection):
356 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
357 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
358 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
361 f_size = getattr(f, 'size', None) or 16
364 f_type = ('int4', 'INTEGER')
366 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
367 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
368 t = eval('fields.'+(f._type), globals())
369 f_type = (type_dict[t], type_dict[t])
370 elif isinstance(f, fields.function) and f._type == 'float':
372 f_type = ('numeric', 'NUMERIC')
374 f_type = ('float8', 'DOUBLE PRECISION')
375 elif isinstance(f, fields.function) and f._type == 'selection':
376 f_type = ('text', 'text')
377 elif isinstance(f, fields.function) and f._type == 'char':
378 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
380 logger = netsvc.Logger()
381 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
386 class orm_template(object):
392 _parent_name = 'parent_id'
393 _parent_store = False
394 _parent_order = False
404 CONCURRENCY_CHECK_FIELD = '__last_update'
405 def log(self, cr, uid, id, message, secondary=False, context=None):
406 return self.pool.get('res.log').create(cr, uid,
409 'res_model': self._name,
410 'secondary': secondary,
416 def view_init(self, cr, uid, fields_list, context=None):
417 """Override this method to do specific things when a view on the object is opened."""
420 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
421 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
423 def _field_create(self, cr, context=None):
426 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
428 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
429 model_id = cr.fetchone()[0]
430 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'))
432 model_id = cr.fetchone()[0]
433 if 'module' in context:
434 name_id = 'model_'+self._name.replace('.', '_')
435 cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
437 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
438 (name_id, context['module'], 'ir.model', model_id)
443 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
445 for rec in cr.dictfetchall():
446 cols[rec['name']] = rec
448 for (k, f) in self._columns.items():
450 'model_id': model_id,
453 'field_description': f.string.replace("'", " "),
455 'relation': f._obj or '',
456 'view_load': (f.view_load and 1) or 0,
457 'select_level': tools.ustr(f.select or 0),
458 'readonly': (f.readonly and 1) or 0,
459 'required': (f.required and 1) or 0,
460 'selectable': (f.selectable and 1) or 0,
461 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
463 # When its a custom field,it does not contain f.select
464 if context.get('field_state', 'base') == 'manual':
465 if context.get('field_name', '') == k:
466 vals['select_level'] = context.get('select', '0')
467 #setting value to let the problem NOT occur next time
469 vals['select_level'] = cols[k]['select_level']
472 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
473 id = cr.fetchone()[0]
475 cr.execute("""INSERT INTO ir_model_fields (
476 id, model_id, model, name, field_description, ttype,
477 relation,view_load,state,select_level,relation_field
479 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
481 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
482 vals['relation'], bool(vals['view_load']), 'base',
483 vals['select_level'], vals['relation_field']
485 if 'module' in context:
486 name1 = 'field_' + self._table + '_' + k
487 cr.execute("select name from ir_model_data where name=%s", (name1,))
489 name1 = name1 + "_" + str(id)
490 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
491 (name1, context['module'], 'ir.model.fields', id)
494 for key, val in vals.items():
495 if cols[k][key] != vals[key]:
496 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
498 cr.execute("""UPDATE ir_model_fields SET
499 model_id=%s, field_description=%s, ttype=%s, relation=%s,
500 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
502 model=%s AND name=%s""", (
503 vals['model_id'], vals['field_description'], vals['ttype'],
504 vals['relation'], bool(vals['view_load']),
505 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
510 def _auto_init(self, cr, context=None):
511 self._field_create(cr, context=context)
513 def __init__(self, cr):
514 if not self._name and not hasattr(self, '_inherit'):
515 name = type(self).__name__.split('.')[0]
516 msg = "The class %s has to have a _name attribute" % name
518 logger = netsvc.Logger()
519 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
520 raise except_orm('ValueError', msg)
522 if not self._description:
523 self._description = self._name
525 self._table = self._name.replace('.', '_')
527 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
528 """Fetch records as objects allowing to use dot notation to browse fields and relations
530 :param cr: database cursor
531 :param user: current user id
532 :param select: id or list of ids
533 :param context: context arguments, like lang, time zone
534 :rtype: object or list of objects requested
537 self._list_class = list_class or browse_record_list
539 # need to accepts ints and longs because ids coming from a method
540 # launched by button in the interface have a type long...
541 if isinstance(select, (int, long)):
542 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
543 elif isinstance(select, list):
544 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)
548 def __export_row(self, cr, uid, row, fields, context=None):
552 def check_type(field_type):
553 if field_type == 'float':
555 elif field_type == 'integer':
557 elif field_type == 'boolean':
561 def selection_field(in_field):
562 col_obj = self.pool.get(in_field.keys()[0])
563 if f[i] in col_obj._columns.keys():
564 return col_obj._columns[f[i]]
565 elif f[i] in col_obj._inherits.keys():
566 selection_field(col_obj._inherits)
571 data = map(lambda x: '', range(len(fields)))
573 for fpos in range(len(fields)):
582 model_data = self.pool.get('ir.model.data')
583 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
585 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
587 r = '%s.%s' % (d['module'], d['name'])
594 # To display external name of selection field when its exported
596 if f[i] in self._columns.keys():
597 cols = self._columns[f[i]]
598 elif f[i] in self._inherit_fields.keys():
599 cols = selection_field(self._inherits)
600 if cols and cols._type == 'selection':
601 sel_list = cols.selection
602 if r and type(sel_list) == type([]):
603 r = [x[1] for x in sel_list if r==x[0]]
604 r = r and r[0] or False
606 if f[i] in self._columns:
607 r = check_type(self._columns[f[i]]._type)
608 elif f[i] in self._inherit_fields:
609 r = check_type(self._inherit_fields[f[i]][2]._type)
610 data[fpos] = r or False
612 if isinstance(r, (browse_record_list, list)):
614 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
617 if [x for x in fields2 if x]:
621 lines2 = self.__export_row(cr, uid, row2, fields2,
624 for fpos2 in range(len(fields)):
625 if lines2 and lines2[0][fpos2]:
626 data[fpos2] = lines2[0][fpos2]
630 name_relation = self.pool.get(rr._table_name)._rec_name
631 if isinstance(rr[name_relation], browse_record):
632 rr = rr[name_relation]
633 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
634 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
635 dt += tools.ustr(rr_name or '') + ','
645 if isinstance(r, browse_record):
646 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
647 r = r and r[0] and r[0][1] or ''
648 data[fpos] = tools.ustr(r or '')
649 return [data] + lines
651 def export_data(self, cr, uid, ids, fields_to_export, context=None):
653 Export fields for selected objects
655 :param cr: database cursor
656 :param uid: current user id
657 :param ids: list of ids
658 :param fields_to_export: list of fields
659 :param context: context arguments, like lang, time zone
660 :rtype: dictionary with a *datas* matrix
662 This method is used when exporting data via client menu
667 cols = self._columns.copy()
668 for f in self._inherit_fields:
669 cols.update({f: self._inherit_fields[f][2]})
671 if x=='.id': return [x]
672 return x.replace(':id','/id').replace('.id','/.id').split('/')
673 fields_to_export = map(fsplit, fields_to_export)
674 fields_export = fields_to_export + []
678 for row in self.browse(cr, uid, ids, context):
679 datas += self.__export_row(cr, uid, row, fields_to_export, context)
680 return {'datas': datas}
682 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
684 Import given data in given module
686 :param cr: database cursor
687 :param uid: current user id
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.
699 Example of fields to import for a sale.order::
702 partner_id, (=name_search)
703 order_line/.id, (=database_id)
705 order_line/product_id/id, (=xml id)
706 order_line/price_unit,
707 order_line/product_uom_qty,
708 order_line/product_uom/id (=xml_id)
712 def _replace_field(x):
713 x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
714 return x.replace(':id','/id').split('/')
715 fields = map(_replace_field, fields)
716 logger = netsvc.Logger()
717 ir_model_data_obj = self.pool.get('ir.model.data')
719 # mode: id (XML id) or .id (database id) or False for name_get
720 def _get_id(model_name, id, current_module=False, mode='id'):
723 obj_model = self.pool.get(model_name)
724 ids = obj_model.search(cr, uid, [('id', '=', int(id))])
726 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
729 module, xml_id = id.rsplit('.', 1)
731 module, xml_id = current_module, id
732 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
733 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
734 if not ir_model_data:
735 raise ValueError('No references to %s.%s' % (module, xml_id))
736 id = ir_model_data[0]['res_id']
738 obj_model = self.pool.get(model_name)
739 ids = obj_model.name_search(cr, uid, id, operator='=')
741 raise ValueError('No record found for %s' % (id,))
746 # datas: a list of records, each record is defined by a list of values
747 # prefix: a list of prefix fields ['line_ids']
748 # position: the line to process, skip is False if it's the first line of the current record
750 # (res, position, warning, res_id) with
751 # res: the record for the next line to process (including it's one2many)
752 # position: the new position for the next line
753 # res_id: the ID of the record if it's a modification
754 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
755 line = datas[position]
763 for i in range(len(fields)):
768 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
771 if field[:len(prefix)] <> prefix:
776 # ID of the record using a XML ID
777 if field[len(prefix)]=='id':
779 data_res_id = _get_id(model_name, line[i], current_module, 'id')
780 except ValueError, e:
785 # ID of the record using a database ID
786 elif field[len(prefix)]=='.id':
787 data_res_id = _get_id(model_name, line[i], current_module, '.id')
790 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
791 if fields_def[field[len(prefix)]]['type']=='one2many':
792 if field[len(prefix)] in done:
794 done[field[len(prefix)]] = True
795 relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
796 newfd = relation_obj.fields_get( cr, uid, context=context )
800 while pos < len(datas):
801 res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
804 (newrow, pos, w2, data_res_id2, xml_id2) = res2
805 nbrmax = max(nbrmax, pos)
808 if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
810 res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
812 elif fields_def[field[len(prefix)]]['type']=='many2one':
813 relation = fields_def[field[len(prefix)]]['relation']
814 if len(field) == len(prefix)+1:
817 mode = field[len(prefix)+1]
818 res = _get_id(relation, line[i], current_module, mode)
820 elif fields_def[field[len(prefix)]]['type']=='many2many':
821 relation = fields_def[field[len(prefix)]]['relation']
822 if len(field) == len(prefix)+1:
825 mode = field[len(prefix)+1]
827 # TODO: improve this by using csv.csv_reader
829 for db_id in line[i].split(config.get('csv_internal_sep')):
830 res.append( _get_id(relation, db_id, current_module, mode) )
833 elif fields_def[field[len(prefix)]]['type'] == 'integer':
834 res = line[i] and int(line[i]) or 0
835 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
836 res = line[i].lower() not in ('0', 'false', 'off')
837 elif fields_def[field[len(prefix)]]['type'] == 'float':
838 res = line[i] and float(line[i]) or 0.0
839 elif fields_def[field[len(prefix)]]['type'] == 'selection':
840 for key, val in fields_def[field[len(prefix)]]['selection']:
841 if line[i] in [tools.ustr(key), tools.ustr(val)]:
844 if line[i] and not res:
845 logger.notifyChannel("import", netsvc.LOG_WARNING,
846 _("key '%s' not found in selection field '%s'") % \
847 (line[i], field[len(prefix)]))
848 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
852 row[field[len(prefix)]] = res or False
854 result = (row, nbrmax, warning, data_res_id, xml_id)
857 fields_def = self.fields_get(cr, uid, context=context)
859 if config.get('import_partial', False) and filename:
860 data = pickle.load(file(config.get('import_partial')))
861 original_value = data.get(filename, 0)
864 while position<len(datas):
867 (res, position, warning, res_id, xml_id) = \
868 process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
871 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
874 id = ir_model_data_obj._update(cr, uid, self._name,
875 current_module, res, mode=mode, xml_id=xml_id,
876 noupdate=noupdate, res_id=res_id, context=context)
878 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
880 if config.get('import_partial', False) and filename and (not (position%100)):
881 data = pickle.load(file(config.get('import_partial')))
882 data[filename] = position
883 pickle.dump(data, file(config.get('import_partial'), 'wb'))
884 if context.get('defer_parent_store_computation'):
885 self._parent_store_compute(cr)
888 if context.get('defer_parent_store_computation'):
889 self._parent_store_compute(cr)
890 return (position, 0, 0, 0)
892 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
894 Read records with given ids with the given fields
896 :param cr: database cursor
897 :param user: current user id
898 :param ids: id or list of the ids of the records to read
899 :param fields: optional list of field names to return (default: all fields would be returned)
900 :type fields: list (example ['field_name_1', ...])
901 :param context: optional context dictionary - it may contains keys for specifying certain options
902 like ``context_lang``, ``context_tz`` to alter the results of the call.
903 A special ``bin_size`` boolean flag may also be passed in the context to request the
904 value of all fields.binary columns to be returned as the size of the binary instead of its
905 contents. This can also be selectively overriden by passing a field-specific flag
906 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
907 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
908 :return: list of dictionaries((dictionary per record asked)) with requested field values
909 :rtype: [{‘name_of_the_field’: value, ...}, ...]
910 :raise AccessError: * if user has no read rights on the requested object
911 * if user tries to bypass access rules for read on the requested object
914 raise NotImplementedError(_('The read method is not implemented on this object !'))
916 def get_invalid_fields(self, cr, uid):
917 return list(self._invalids)
919 def _validate(self, cr, uid, ids, context=None):
920 context = context or {}
921 lng = context.get('lang', False) or 'en_US'
922 trans = self.pool.get('ir.translation')
924 for constraint in self._constraints:
925 fun, msg, fields = constraint
926 if not fun(self, cr, uid, ids):
927 # Check presence of __call__ directly instead of using
928 # callable() because it will be deprecated as of Python 3.0
929 if hasattr(msg, '__call__'):
930 tmp_msg = msg(self, cr, uid, ids, context=context)
931 if isinstance(tmp_msg, tuple):
932 tmp_msg, params = tmp_msg
933 translated_msg = tmp_msg % params
935 translated_msg = tmp_msg
937 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
939 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
941 self._invalids.update(fields)
944 raise except_orm('ValidateError', '\n'.join(error_msgs))
946 self._invalids.clear()
948 def default_get(self, cr, uid, fields_list, context=None):
950 Returns default values for the fields in fields_list.
952 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
953 :type fields_list: list
954 :param context: optional context dictionary - it may contains keys for specifying certain options
955 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
956 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
957 or override a default value for a field.
958 A special ``bin_size`` boolean flag may also be passed in the context to request the
959 value of all fields.binary columns to be returned as the size of the binary instead of its
960 contents. This can also be selectively overriden by passing a field-specific flag
961 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
962 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
963 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
965 # trigger view init hook
966 self.view_init(cr, uid, fields_list, context)
972 # get the default values for the inherited fields
973 for t in self._inherits.keys():
974 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
977 # get the default values defined in the object
978 for f in fields_list:
979 if f in self._defaults:
980 if callable(self._defaults[f]):
981 defaults[f] = self._defaults[f](self, cr, uid, context)
983 defaults[f] = self._defaults[f]
985 fld_def = ((f in self._columns) and self._columns[f]) \
986 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
989 if isinstance(fld_def, fields.property):
990 property_obj = self.pool.get('ir.property')
991 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
993 if isinstance(prop_value, (browse_record, browse_null)):
994 defaults[f] = prop_value.id
996 defaults[f] = prop_value
998 if f not in defaults:
1001 # get the default values set by the user and override the default
1002 # values defined in the object
1003 ir_values_obj = self.pool.get('ir.values')
1004 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1005 for id, field, field_value in res:
1006 if field in fields_list:
1007 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1008 if fld_def._type in ('many2one', 'one2one'):
1009 obj = self.pool.get(fld_def._obj)
1010 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1012 if fld_def._type in ('many2many'):
1013 obj = self.pool.get(fld_def._obj)
1015 for i in range(len(field_value)):
1016 if not obj.search(cr, uid, [('id', '=',
1019 field_value2.append(field_value[i])
1020 field_value = field_value2
1021 if fld_def._type in ('one2many'):
1022 obj = self.pool.get(fld_def._obj)
1024 for i in range(len(field_value)):
1025 field_value2.append({})
1026 for field2 in field_value[i]:
1027 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1028 obj2 = self.pool.get(obj._columns[field2]._obj)
1029 if not obj2.search(cr, uid,
1030 [('id', '=', field_value[i][field2])]):
1032 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1033 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1034 if not obj2.search(cr, uid,
1035 [('id', '=', field_value[i][field2])]):
1037 # TODO add test for many2many and one2many
1038 field_value2[i][field2] = field_value[i][field2]
1039 field_value = field_value2
1040 defaults[field] = field_value
1042 # get the default values from the context
1043 for key in context or {}:
1044 if key.startswith('default_') and (key[8:] in fields_list):
1045 defaults[key[8:]] = context[key]
1049 def perm_read(self, cr, user, ids, context=None, details=True):
1050 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1052 def unlink(self, cr, uid, ids, context=None):
1053 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1055 def write(self, cr, user, ids, vals, context=None):
1056 raise NotImplementedError(_('The write method is not implemented on this object !'))
1058 def create(self, cr, user, vals, context=None):
1059 raise NotImplementedError(_('The create method is not implemented on this object !'))
1061 def fields_get_keys(self, cr, user, context=None):
1062 res = self._columns.keys()
1063 for parent in self._inherits:
1064 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1067 # returns the definition of each field in the object
1068 # the optional fields parameter can limit the result to some fields
1069 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1073 translation_obj = self.pool.get('ir.translation')
1074 for parent in self._inherits:
1075 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1077 if self._columns.keys():
1078 for f in self._columns.keys():
1079 field_col = self._columns[f]
1080 if allfields and f not in allfields:
1082 res[f] = {'type': field_col._type}
1083 # This additional attributes for M2M and function field is added
1084 # because we need to display tooltip with this additional information
1085 # when client is started in debug mode.
1086 if isinstance(field_col, fields.function):
1087 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1088 res[f]['store'] = field_col.store
1089 if isinstance(field_col.store, dict):
1090 res[f]['store'] = str(field_col.store)
1091 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1092 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1093 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1094 res[f]['func_obj'] = field_col._obj or False
1095 res[f]['func_method'] = field_col._method
1096 if isinstance(field_col, fields.many2many):
1097 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1098 res[f]['third_table'] = field_col._rel
1099 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1100 'change_default', 'translate', 'help', 'select', 'selectable'):
1101 if getattr(field_col, arg):
1102 res[f][arg] = getattr(field_col, arg)
1103 if not write_access:
1104 res[f]['readonly'] = True
1105 res[f]['states'] = {}
1106 for arg in ('digits', 'invisible', 'filters'):
1107 if getattr(field_col, arg, None):
1108 res[f][arg] = getattr(field_col, arg)
1110 if field_col.string:
1111 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1113 res[f]['string'] = res_trans
1115 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1117 res[f]['help'] = help_trans
1119 if hasattr(field_col, 'selection'):
1120 if isinstance(field_col.selection, (tuple, list)):
1121 sel = field_col.selection
1122 # translate each selection option
1124 for (key, val) in sel:
1127 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1128 sel2.append((key, val2 or val))
1130 res[f]['selection'] = sel
1132 # call the 'dynamic selection' function
1133 res[f]['selection'] = field_col.selection(self, cr, user, context)
1134 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1135 res[f]['relation'] = field_col._obj
1136 res[f]['domain'] = field_col._domain
1137 res[f]['context'] = field_col._context
1139 #TODO : read the fields from the database
1143 # filter out fields which aren't in the fields list
1144 for r in res.keys():
1145 if r not in allfields:
1150 # Overload this method if you need a window title which depends on the context
1152 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1155 def __view_look_dom(self, cr, user, node, view_id, context=None):
1163 if isinstance(s, unicode):
1164 return s.encode('utf8')
1167 # return True if node can be displayed to current user
1168 def check_group(node):
1169 if node.get('groups'):
1170 groups = node.get('groups').split(',')
1171 access_pool = self.pool.get('ir.model.access')
1172 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1174 node.set('invisible', '1')
1175 if 'attrs' in node.attrib:
1176 del(node.attrib['attrs']) #avoid making field visible later
1177 del(node.attrib['groups'])
1182 if node.tag in ('field', 'node', 'arrow'):
1183 if node.get('object'):
1188 if f.tag in ('field'):
1189 xml += etree.tostring(f, encoding="utf-8")
1191 new_xml = etree.fromstring(encode(xml))
1192 ctx = context.copy()
1193 ctx['base_model_name'] = self._name
1194 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1199 attrs = {'views': views}
1201 if node.get('name'):
1204 if node.get('name') in self._columns:
1205 column = self._columns[node.get('name')]
1207 column = self._inherit_fields[node.get('name')][2]
1212 relation = self.pool.get(column._obj)
1217 if f.tag in ('form', 'tree', 'graph'):
1219 ctx = context.copy()
1220 ctx['base_model_name'] = self._name
1221 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1222 views[str(f.tag)] = {
1226 attrs = {'views': views}
1227 if node.get('widget') and node.get('widget') == 'selection':
1228 # Prepare the cached selection list for the client. This needs to be
1229 # done even when the field is invisible to the current user, because
1230 # other events could need to change its value to any of the selectable ones
1231 # (such as on_change events, refreshes, etc.)
1233 # If domain and context are strings, we keep them for client-side, otherwise
1234 # we evaluate them server-side to consider them when generating the list of
1236 # TODO: find a way to remove this hack, by allow dynamic domains
1238 if column._domain and not isinstance(column._domain, basestring):
1239 dom = column._domain
1240 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1241 search_context = dict(context)
1242 if column._context and not isinstance(column._context, basestring):
1243 search_context.update(column._context)
1244 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1245 if (node.get('required') and not int(node.get('required'))) or not column.required:
1246 attrs['selection'].append((False, ''))
1247 fields[node.get('name')] = attrs
1249 elif node.tag in ('form', 'tree'):
1250 result = self.view_header_get(cr, user, False, node.tag, context)
1252 node.set('string', result)
1254 elif node.tag == 'calendar':
1255 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1256 if node.get(additional_field):
1257 fields[node.get(additional_field)] = {}
1259 if 'groups' in node.attrib:
1263 if ('lang' in context) and not result:
1264 if node.get('string'):
1265 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1266 if trans == node.get('string') and ('base_model_name' in context):
1267 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1268 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1269 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1271 node.set('string', trans)
1272 if node.get('confirm'):
1273 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1275 node.set('confirm', trans)
1277 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1279 node.set('sum', trans)
1282 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1283 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1287 def _disable_workflow_buttons(self, cr, user, node):
1289 # admin user can always activate workflow buttons
1292 # TODO handle the case of more than one workflow for a model or multiple
1293 # transitions with different groups and same signal
1294 usersobj = self.pool.get('res.users')
1295 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1296 for button in buttons:
1297 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1298 cr.execute("""SELECT DISTINCT t.group_id
1300 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1301 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1304 AND t.group_id is NOT NULL
1305 """, (self._name, button.get('name')))
1306 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1307 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1308 button.set('readonly', str(int(not can_click)))
1311 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1312 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1313 node = self._disable_workflow_buttons(cr, user, node)
1314 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1316 if node.tag == 'diagram':
1317 if node.getchildren()[0].tag == 'node':
1318 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1319 if node.getchildren()[1].tag == 'arrow':
1320 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1321 for key, value in node_fields.items():
1323 for key, value in arrow_fields.items():
1326 fields = self.fields_get(cr, user, fields_def.keys(), context)
1327 for field in fields_def:
1329 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1330 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1331 elif field in fields:
1332 fields[field].update(fields_def[field])
1334 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))
1335 res = cr.fetchall()[:]
1337 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1338 msg = "\n * ".join([r[0] for r in res])
1339 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1340 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1341 raise except_orm('View error', msg)
1344 def __get_default_calendar_view(self):
1345 """Generate a default calendar view (For internal use only).
1348 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1349 '<calendar string="%s"') % (self._description)
1351 if (self._date_name not in self._columns):
1353 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1354 if dt in self._columns:
1355 self._date_name = dt
1360 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1363 arch += ' date_start="%s"' % (self._date_name)
1365 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1366 if color in self._columns:
1367 arch += ' color="' + color + '"'
1370 dt_stop_flag = False
1372 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1373 if dt_stop in self._columns:
1374 arch += ' date_stop="' + dt_stop + '"'
1378 if not dt_stop_flag:
1379 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1380 if dt_delay in self._columns:
1381 arch += ' date_delay="' + dt_delay + '"'
1385 ' <field name="%s"/>\n'
1386 '</calendar>') % (self._rec_name)
1390 def __get_default_search_view(self, cr, uid, context=None):
1391 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1392 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1394 fields_to_search = set()
1395 fields = self.fields_get(cr, uid, context=context)
1396 for field in fields:
1397 if fields[field].get('select'):
1398 fields_to_search.add(field)
1399 for view in (form_view, tree_view):
1400 view_root = etree.fromstring(view['arch'])
1401 # Only care about select=1 in xpath below, because select=2 is covered
1402 # by the custom advanced search in clients
1403 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1405 tree_view_root = view_root # as provided by loop above
1406 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1407 field_group = etree.Element("group")
1408 search_view.append(field_group)
1410 for field_name in fields_to_search:
1411 field_group.append(etree.Element("field", attrib={'name': field_name}))
1413 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1416 # if view_id, view_type is not required
1418 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1420 Get the detailed composition of the requested view like fields, model, view architecture
1422 :param cr: database cursor
1423 :param user: current user id
1424 :param view_id: id of the view or None
1425 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1426 :param context: context arguments, like lang, time zone
1427 :param toolbar: true to include contextual actions
1428 :param submenu: example (portal_project module)
1429 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1430 :raise AttributeError:
1431 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1432 * if some tag other than 'position' is found in parent view
1433 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1440 if isinstance(s, unicode):
1441 return s.encode('utf8')
1444 def raise_view_error(error_msg, child_view_id):
1445 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1446 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1447 % { 'xml_id': child_view.xml_id,
1448 'parent_xml_id': view.xml_id,
1449 'model': self._name, })
1451 def _inherit_apply(src, inherit, inherit_id=None):
1452 def _find(node, node2):
1453 if node2.tag == 'xpath':
1454 res = node.xpath(node2.get('expr'))
1460 for n in node.getiterator(node2.tag):
1462 if node2.tag == 'field':
1463 # only compare field names, a field can be only once in a given view
1464 # at a given level (and for multilevel expressions, we should use xpath
1465 # inheritance spec anyway)
1466 if node2.get('name') == n.get('name'):
1470 for attr in node2.attrib:
1471 if attr == 'position':
1474 if n.get(attr) == node2.get(attr):
1481 # End: _find(node, node2)
1483 doc_dest = etree.fromstring(encode(inherit))
1484 toparse = [doc_dest]
1487 node2 = toparse.pop(0)
1488 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1490 if node2.tag == 'data':
1491 toparse += [ c for c in doc_dest ]
1493 node = _find(src, node2)
1494 if node is not None:
1496 if node2.get('position'):
1497 pos = node2.get('position')
1498 if pos == 'replace':
1499 parent = node.getparent()
1501 src = copy.deepcopy(node2[0])
1504 node.addprevious(child)
1505 node.getparent().remove(node)
1506 elif pos == 'attributes':
1507 for child in node2.getiterator('attribute'):
1508 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1510 node.set(attribute[0], attribute[1])
1512 del(node.attrib[attribute[0]])
1514 sib = node.getnext()
1518 elif pos == 'after':
1523 sib.addprevious(child)
1524 elif pos == 'before':
1525 node.addprevious(child)
1527 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1530 ' %s="%s"' % (attr, node2.get(attr))
1531 for attr in node2.attrib
1532 if attr != 'position'
1534 tag = "<%s%s>" % (node2.tag, attrs)
1535 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1537 # End: _inherit_apply(src, inherit)
1539 result = {'type': view_type, 'model': self._name}
1544 parent_view_model = None
1546 view_ref = context.get(view_type + '_view_ref', False)
1547 if view_ref and not view_id:
1549 module, view_ref = view_ref.split('.', 1)
1550 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1551 view_ref_res = cr.fetchone()
1553 view_id = view_ref_res[0]
1556 query = "SELECT arch,name,field_parent,id,type,inherit_id,model FROM ir_ui_view WHERE id=%s"
1559 query += " AND model=%s"
1560 params += (self._name,)
1561 cr.execute(query, params)
1563 cr.execute('''SELECT
1564 arch,name,field_parent,id,type,inherit_id,model
1571 ORDER BY priority''', (self._name, view_type))
1572 sql_res = cr.fetchone()
1578 view_id = ok or sql_res[3]
1580 parent_view_model = sql_res[6]
1582 # if a view was found
1584 result['type'] = sql_res[4]
1585 result['view_id'] = sql_res[3]
1586 result['arch'] = sql_res[0]
1588 def _inherit_apply_rec(result, inherit_id):
1589 # get all views which inherit from (ie modify) this view
1590 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1591 sql_inherit = cr.fetchall()
1592 for (inherit, id) in sql_inherit:
1593 result = _inherit_apply(result, inherit, id)
1594 result = _inherit_apply_rec(result, id)
1597 inherit_result = etree.fromstring(encode(result['arch']))
1598 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1600 result['name'] = sql_res[1]
1601 result['field_parent'] = sql_res[2] or False
1604 # otherwise, build some kind of default view
1605 if view_type == 'form':
1606 res = self.fields_get(cr, user, context=context)
1607 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1608 '<form string="%s">' % (self._description,)
1610 if res[x]['type'] not in ('one2many', 'many2many'):
1611 xml += '<field name="%s"/>' % (x,)
1612 if res[x]['type'] == 'text':
1616 elif view_type == 'tree':
1617 _rec_name = self._rec_name
1618 if _rec_name not in self._columns:
1619 _rec_name = self._columns.keys()[0]
1620 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1621 '<tree string="%s"><field name="%s"/></tree>' \
1622 % (self._description, self._rec_name)
1624 elif view_type == 'calendar':
1625 xml = self.__get_default_calendar_view()
1627 elif view_type == 'search':
1628 xml = self.__get_default_search_view(cr, user, context)
1631 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1632 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1633 result['arch'] = etree.fromstring(encode(xml))
1634 result['name'] = 'default'
1635 result['field_parent'] = False
1636 result['view_id'] = 0
1638 if parent_view_model != self._name:
1639 ctx = context.copy()
1640 ctx['base_model_name'] = parent_view_model
1643 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1644 result['arch'] = xarch
1645 result['fields'] = xfields
1648 if context and context.get('active_id', False):
1649 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1651 act_id = data_menu.id
1653 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1654 result['submenu'] = getattr(data_action, 'menus', False)
1658 for key in ('report_sxw_content', 'report_rml_content',
1659 'report_sxw', 'report_rml',
1660 'report_sxw_content_data', 'report_rml_content_data'):
1664 ir_values_obj = self.pool.get('ir.values')
1665 resprint = ir_values_obj.get(cr, user, 'action',
1666 'client_print_multi', [(self._name, False)], False,
1668 resaction = ir_values_obj.get(cr, user, 'action',
1669 'client_action_multi', [(self._name, False)], False,
1672 resrelate = ir_values_obj.get(cr, user, 'action',
1673 'client_action_relate', [(self._name, False)], False,
1675 resprint = map(clean, resprint)
1676 resaction = map(clean, resaction)
1677 resaction = filter(lambda x: not x.get('multi', False), resaction)
1678 resprint = filter(lambda x: not x.get('multi', False), resprint)
1679 resrelate = map(lambda x: x[2], resrelate)
1681 for x in resprint + resaction + resrelate:
1682 x['string'] = x['name']
1684 result['toolbar'] = {
1686 'action': resaction,
1691 _view_look_dom_arch = __view_look_dom_arch
1693 def search_count(self, cr, user, args, context=None):
1696 res = self.search(cr, user, args, context=context, count=True)
1697 if isinstance(res, list):
1701 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1703 Search for records based on a search domain.
1705 :param cr: database cursor
1706 :param user: current user id
1707 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1708 :param offset: optional number of results to skip in the returned values (default: 0)
1709 :param limit: optional max number of records to return (default: **None**)
1710 :param order: optional columns to sort by (default: self._order=id )
1711 :param context: optional context arguments, like lang, time zone
1712 :type context: dictionary
1713 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1714 :return: id or list of ids of records matching the criteria
1715 :rtype: integer or list of integers
1716 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1718 **Expressing a search domain (args)**
1720 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1722 * **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.
1723 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1724 The semantics of most of these operators are obvious.
1725 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1726 according to the semantics of this model (i.e following the relationship field named by
1727 ``self._parent_name``, by default ``parent_id``.
1728 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1730 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1731 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1732 Be very careful about this when you combine them the first time.
1734 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1736 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1738 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::
1740 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1743 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1745 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1747 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1748 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1749 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1751 :param access_rights_uid: optional user ID to use when checking access rights
1752 (not for ir.rules, this is only for ir.model.access)
1754 raise NotImplementedError(_('The search method is not implemented on this object !'))
1756 def name_get(self, cr, user, ids, context=None):
1759 :param cr: database cursor
1760 :param user: current user id
1762 :param ids: list of ids
1763 :param context: context arguments, like lang, time zone
1764 :type context: dictionary
1765 :return: tuples with the text representation of requested objects for to-many relationships
1772 if isinstance(ids, (int, long)):
1774 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1775 [self._rec_name], context, load='_classic_write')]
1777 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1779 Search for records and their display names according to a search domain.
1781 :param cr: database cursor
1782 :param user: current user id
1783 :param name: object name to search
1784 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1785 :param operator: operator for search criterion
1786 :param context: context arguments, like lang, time zone
1787 :type context: dictionary
1788 :param limit: optional max number of records to return
1789 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1791 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1792 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1795 return self._name_search(cr, user, name, args, operator, context, limit)
1797 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1798 # solve some access rights issues
1799 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1806 args += [(self._rec_name, operator, name)]
1807 access_rights_uid = name_get_uid or user
1808 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1809 res = self.name_get(cr, access_rights_uid, ids, context)
1812 def copy(self, cr, uid, id, default=None, context=None):
1813 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1815 def exists(self, cr, uid, id, context=None):
1816 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1818 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1821 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1823 fields = self._columns.keys() + self._inherit_fields.keys()
1824 #FIXME: collect all calls to _get_source into one SQL call.
1826 res[lang] = {'code': lang}
1828 if f in self._columns:
1829 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1831 res[lang][f] = res_trans
1833 res[lang][f] = self._columns[f].string
1834 for table in self._inherits:
1835 cols = intersect(self._inherit_fields.keys(), fields)
1836 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1839 res[lang]['code'] = lang
1840 for f in res2[lang]:
1841 res[lang][f] = res2[lang][f]
1844 def write_string(self, cr, uid, id, langs, vals, context=None):
1845 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1846 #FIXME: try to only call the translation in one SQL
1849 if field in self._columns:
1850 src = self._columns[field].string
1851 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1852 for table in self._inherits:
1853 cols = intersect(self._inherit_fields.keys(), vals)
1855 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1858 def _check_removed_columns(self, cr, log=False):
1859 raise NotImplementedError()
1861 def _add_missing_default_values(self, cr, uid, values, context=None):
1862 missing_defaults = []
1863 avoid_tables = [] # avoid overriding inherited values when parent is set
1864 for tables, parent_field in self._inherits.items():
1865 if parent_field in values:
1866 avoid_tables.append(tables)
1867 for field in self._columns.keys():
1868 if not field in values:
1869 missing_defaults.append(field)
1870 for field in self._inherit_fields.keys():
1871 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1872 missing_defaults.append(field)
1874 if len(missing_defaults):
1875 # override defaults with the provided values, never allow the other way around
1876 defaults = self.default_get(cr, uid, missing_defaults, context)
1878 if ((dv in self._columns and self._columns[dv]._type == 'many2many') \
1879 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many')) \
1880 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1881 defaults[dv] = [(6, 0, defaults[dv])]
1882 if (dv in self._columns and self._columns[dv]._type == 'one2many' \
1883 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many')) \
1884 and isinstance(defaults[dv], (list, tuple)) and defaults[dv] and isinstance(defaults[dv][0], dict):
1885 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
1886 defaults.update(values)
1890 class orm_memory(orm_template):
1892 _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']
1893 _inherit_fields = {}
1894 _max_count = config.get('osv_memory_count_limit')
1895 _max_hours = config.get('osv_memory_age_limit')
1898 def __init__(self, cr):
1899 super(orm_memory, self).__init__(cr)
1903 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1905 def _check_access(self, uid, object_id, mode):
1906 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1907 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1909 def vaccum(self, cr, uid, force=False):
1910 """Run the vaccuum cleaning system, expiring and removing old records from the
1911 virtual osv_memory tables if the "max count" or "max age" conditions are enabled
1912 and have been reached. This method can be called very often (e.g. everytime a record
1913 is created), but will only actually trigger the cleanup process once out of
1914 "_check_time" times (by default once out of 20 calls)."""
1916 if (not force) and (self.check_id % self._check_time):
1920 # Age-based expiration
1922 max = time.time() - self._max_hours * 60 * 60
1923 for k,v in self.datas.iteritems():
1924 if v['internal.date_access'] < max:
1926 self.unlink(cr, 1, tounlink)
1928 # Count-based expiration
1929 if self._max_count and len(self.datas) > self._max_count:
1930 # sort by access time to remove only the first/oldest ones in LRU fashion
1931 records = self.datas.items()
1932 records.sort(key=lambda x:x[1]['internal.date_access'])
1933 self.unlink(cr, 1, [x[0] for x in records[:len(self.datas)-self._max_count]])
1937 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1940 if not fields_to_read:
1941 fields_to_read = self._columns.keys()
1945 if isinstance(ids, (int, long)):
1949 for f in fields_to_read:
1950 record = self.datas.get(id)
1952 self._check_access(user, id, 'read')
1953 r[f] = record.get(f, False)
1954 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1957 if id in self.datas:
1958 self.datas[id]['internal.date_access'] = time.time()
1959 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1960 for f in fields_post:
1961 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1962 for record in result:
1963 record[f] = res2[record['id']]
1964 if isinstance(ids_orig, (int, long)):
1968 def write(self, cr, user, ids, vals, context=None):
1974 if self._columns[field]._classic_write:
1975 vals2[field] = vals[field]
1977 upd_todo.append(field)
1978 for object_id in ids:
1979 self._check_access(user, object_id, mode='write')
1980 self.datas[object_id].update(vals2)
1981 self.datas[object_id]['internal.date_access'] = time.time()
1982 for field in upd_todo:
1983 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
1984 self._validate(cr, user, [object_id], context)
1985 wf_service = netsvc.LocalService("workflow")
1986 wf_service.trg_write(user, self._name, object_id, cr)
1989 def create(self, cr, user, vals, context=None):
1990 self.vaccum(cr, user)
1992 id_new = self.next_id
1994 vals = self._add_missing_default_values(cr, user, vals, context)
1999 if self._columns[field]._classic_write:
2000 vals2[field] = vals[field]
2002 upd_todo.append(field)
2003 self.datas[id_new] = vals2
2004 self.datas[id_new]['internal.date_access'] = time.time()
2005 self.datas[id_new]['internal.create_uid'] = user
2007 for field in upd_todo:
2008 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2009 self._validate(cr, user, [id_new], context)
2010 if self._log_create and not (context and context.get('no_store_function', False)):
2011 message = self._description + \
2013 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2015 self.log(cr, user, id_new, message, True, context=context)
2016 wf_service = netsvc.LocalService("workflow")
2017 wf_service.trg_create(user, self._name, id_new, cr)
2020 def _where_calc(self, cr, user, args, active_test=True, context=None):
2025 # if the object has a field named 'active', filter out all inactive
2026 # records unless they were explicitely asked for
2027 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2029 active_in_args = False
2031 if a[0] == 'active':
2032 active_in_args = True
2033 if not active_in_args:
2034 args.insert(0, ('active', '=', 1))
2036 args = [('active', '=', 1)]
2039 e = expression.expression(args)
2040 e.parse(cr, user, self, context)
2044 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2048 # implicit filter on current user except for superuser
2052 args.insert(0, ('internal.create_uid', '=', user))
2054 result = self._where_calc(cr, user, args, context=context)
2056 return self.datas.keys()
2060 #Find the value of dict
2063 for id, data in self.datas.items():
2064 counter = counter + 1
2066 if limit and (counter > int(limit)):
2071 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2072 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2073 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2074 elif arg[1] in ['ilike']:
2075 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2085 def unlink(self, cr, uid, ids, context=None):
2087 self._check_access(uid, id, 'unlink')
2088 self.datas.pop(id, None)
2090 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2093 def perm_read(self, cr, user, ids, context=None, details=True):
2095 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2096 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2098 self._check_access(user, id, 'read')
2100 'create_uid': credentials,
2101 'create_date': create_date,
2103 'write_date': False,
2109 def _check_removed_columns(self, cr, log=False):
2110 # nothing to check in memory...
2113 def exists(self, cr, uid, id, context=None):
2114 return id in self.datas
2116 class orm(orm_template):
2117 _sql_constraints = []
2119 _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']
2120 __logger = logging.getLogger('orm')
2121 __schema = logging.getLogger('orm.schema')
2122 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2124 Get the list of records in list view grouped by the given ``groupby`` fields
2126 :param cr: database cursor
2127 :param uid: current user id
2128 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2129 :param fields: list of fields present in the list view specified on the object
2130 :param groupby: list of fields on which to groupby the records
2131 :type fields_list: list (example ['field_name_1', ...])
2132 :param offset: optional number of records to skip
2133 :param limit: optional max number of records to return
2134 :param context: context arguments, like lang, time zone
2135 :param order: optional ``order by`` specification, for overriding the natural
2136 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2137 (supported only for many2one fields currently)
2138 :return: list of dictionaries(one dictionary for each record) containing:
2140 * the values of fields grouped by the fields in ``groupby`` argument
2141 * __domain: list of tuples specifying the search criteria
2142 * __context: dictionary with argument like ``groupby``
2143 :rtype: [{'field_name_1': value, ...]
2144 :raise AccessError: * if user has no read rights on the requested object
2145 * if user tries to bypass access rules for read on the requested object
2148 context = context or {}
2149 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2151 fields = self._columns.keys()
2153 query = self._where_calc(cr, uid, domain, context=context)
2154 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2156 # Take care of adding join(s) if groupby is an '_inherits'ed field
2157 groupby_list = groupby
2158 qualified_groupby_field = groupby
2160 if isinstance(groupby, list):
2161 groupby = groupby[0]
2162 qualified_groupby_field = self._inherits_join_calc(groupby, query)
2165 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?)"
2166 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2167 assert groupby_def and groupby_def._classic_write, "Fields in 'groupby' must be regular database-persisted fields (no function or related fields), or function fields with store=True"
2169 fget = self.fields_get(cr, uid, fields)
2170 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2172 group_count = group_by = groupby
2174 if fget.get(groupby):
2175 if fget[groupby]['type'] in ('date', 'datetime'):
2176 flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
2177 groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
2178 qualified_groupby_field = groupby
2180 flist = qualified_groupby_field
2182 # Don't allow arbitrary values, as this would be a SQL injection vector!
2183 raise except_orm(_('Invalid group_by'),
2184 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2187 fields_pre = [f for f in float_int_fields if
2188 f == self.CONCURRENCY_CHECK_FIELD
2189 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2190 for f in fields_pre:
2191 if f not in ['id', 'sequence']:
2192 group_operator = fget[f].get('group_operator', 'sum')
2195 qualified_field = '"%s"."%s"' % (self._table, f)
2196 flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2198 gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2200 from_clause, where_clause, where_clause_params = query.get_sql()
2201 where_clause = where_clause and ' WHERE ' + where_clause
2202 limit_str = limit and ' limit %d' % limit or ''
2203 offset_str = offset and ' offset %d' % offset or ''
2204 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2206 cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + limit_str + offset_str, where_clause_params)
2209 for r in cr.dictfetchall():
2210 for fld, val in r.items():
2211 if val == None: r[fld] = False
2212 alldata[r['id']] = r
2215 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2216 # the IDS of records that have groupby field value = False or '' should be sorted too
2217 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2218 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2219 # 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):
2220 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2224 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2225 if not isinstance(groupby_list, (str, unicode)):
2226 if groupby or not context.get('group_by_no_leaf', False):
2227 d['__context'] = {'group_by': groupby_list[1:]}
2228 if groupby and groupby in fget:
2229 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2230 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2231 days = calendar.monthrange(dt.year, dt.month)[1]
2233 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2234 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),\
2235 (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
2236 del alldata[d['id']][groupby]
2237 d.update(alldata[d['id']])
2241 def _inherits_join_add(self, parent_model_name, query):
2243 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2245 :param parent_model_name: name of the parent model for which the clauses should be added
2246 :param query: query object on which the JOIN should be added
2248 inherits_field = self._inherits[parent_model_name]
2249 parent_model = self.pool.get(parent_model_name)
2250 parent_table_name = parent_model._table
2251 quoted_parent_table_name = '"%s"' % parent_table_name
2252 if quoted_parent_table_name not in query.tables:
2253 query.tables.append(quoted_parent_table_name)
2254 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2256 def _inherits_join_calc(self, field, query):
2258 Adds missing table select and join clause(s) to ``query`` for reaching
2259 the field coming from an '_inherits' parent table (no duplicates).
2261 :param field: name of inherited field to reach
2262 :param query: query object on which the JOIN should be added
2263 :return: qualified name of field, to be used in SELECT clause
2265 current_table = self
2266 while field in current_table._inherit_fields and not field in current_table._columns:
2267 parent_model_name = current_table._inherit_fields[field][0]
2268 parent_table = self.pool.get(parent_model_name)
2269 self._inherits_join_add(parent_model_name, query)
2270 current_table = parent_table
2271 return '"%s".%s' % (current_table._table, field)
2273 def _parent_store_compute(self, cr):
2274 if not self._parent_store:
2276 logger = netsvc.Logger()
2277 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2278 def browse_rec(root, pos=0):
2280 where = self._parent_name+'='+str(root)
2282 where = self._parent_name+' IS NULL'
2283 if self._parent_order:
2284 where += ' order by '+self._parent_order
2285 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2287 for id in cr.fetchall():
2288 pos2 = browse_rec(id[0], pos2)
2289 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2291 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2292 if self._parent_order:
2293 query += ' order by ' + self._parent_order
2296 for (root,) in cr.fetchall():
2297 pos = browse_rec(root, pos)
2300 def _update_store(self, cr, f, k):
2301 logger = netsvc.Logger()
2302 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2303 ss = self._columns[k]._symbol_set
2304 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2305 cr.execute('select id from '+self._table)
2306 ids_lst = map(lambda x: x[0], cr.fetchall())
2309 ids_lst = ids_lst[40:]
2310 res = f.get(cr, self, iids, k, 1, {})
2311 for key, val in res.items():
2314 # if val is a many2one, just write the ID
2315 if type(val) == tuple:
2317 if (val<>False) or (type(val)<>bool):
2318 cr.execute(update_query, (ss[1](val), key))
2320 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2321 """Raise except_orm if value is not among the valid values for the selection field"""
2322 if self._columns[field]._type == 'reference':
2323 val_model, val_id_str = value.split(',', 1)
2326 val_id = long(val_id_str)
2330 raise except_orm(_('ValidateError'),
2331 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2335 if isinstance(self._columns[field].selection, (tuple, list)):
2336 if val in dict(self._columns[field].selection):
2338 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2340 raise except_orm(_('ValidateError'),
2341 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2343 def _check_removed_columns(self, cr, log=False):
2344 # iterate on the database columns to drop the NOT NULL constraints
2345 # of fields which were required but have been removed (or will be added by another module)
2346 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2347 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2348 cr.execute("SELECT a.attname, a.attnotnull"
2349 " FROM pg_class c, pg_attribute a"
2350 " WHERE c.relname=%s"
2351 " AND c.oid=a.attrelid"
2352 " AND a.attisdropped=%s"
2353 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2354 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2356 for column in cr.dictfetchall():
2358 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2359 column['attname'], self._table, self._name)
2360 if column['attnotnull']:
2361 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2362 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2363 self._table, column['attname'])
2365 def _auto_init(self, cr, context=None):
2368 store_compute = False
2371 self._field_create(cr, context=context)
2372 if getattr(self, '_auto', True):
2373 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2375 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2376 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2378 self.__schema.debug("Table '%s': created", self._table)
2381 if self._parent_store:
2382 cr.execute("""SELECT c.relname
2383 FROM pg_class c, pg_attribute a
2384 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2385 """, (self._table, 'parent_left'))
2387 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2388 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2389 if 'parent_left' not in self._columns:
2390 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2392 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2393 self._table, 'parent_left', 'INTEGER')
2394 elif not self._columns['parent_left'].select:
2395 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2397 if 'parent_right' not in self._columns:
2398 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2400 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2401 self._table, 'parent_right', 'INTEGER')
2402 elif not self._columns['parent_right'].select:
2403 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2405 if self._columns[self._parent_name].ondelete != 'cascade':
2406 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2407 self._parent_name, self._name)
2410 store_compute = True
2412 if self._log_access:
2414 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2415 'create_date': 'TIMESTAMP',
2416 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2417 'write_date': 'TIMESTAMP'
2422 FROM pg_class c, pg_attribute a
2423 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2424 """, (self._table, k))
2426 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2428 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2429 self._table, k, logs[k])
2431 self._check_removed_columns(cr, log=False)
2433 # iterate on the "object columns"
2434 todo_update_store = []
2435 update_custom_fields = context.get('update_custom_fields', False)
2437 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 " \
2438 "FROM pg_class c,pg_attribute a,pg_type t " \
2439 "WHERE c.relname=%s " \
2440 "AND c.oid=a.attrelid " \
2441 "AND a.atttypid=t.oid", (self._table,))
2442 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2445 for k in self._columns:
2446 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2448 #Not Updating Custom fields
2449 if k.startswith('x_') and not update_custom_fields:
2452 f = self._columns[k]
2454 if isinstance(f, fields.one2many):
2455 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2457 if self.pool.get(f._obj):
2458 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2459 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2460 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2463 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))
2464 res = cr.fetchone()[0]
2466 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2467 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2468 self._obj, f._fields_id, f._table)
2469 elif isinstance(f, fields.many2many):
2470 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2471 if not cr.dictfetchall():
2472 if not self.pool.get(f._obj):
2473 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2474 ref = self.pool.get(f._obj)._table
2475 # ref = f._obj.replace('.', '_')
2476 cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, UNIQUE("%s","%s")) WITH OIDS' % (f._rel, f._id1, self._table, f._id2, ref, f._id1, f._id2))
2477 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2478 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2479 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2481 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2482 f._rel, self._table, ref)
2484 res = col_data.get(k, [])
2485 res = res and [res] or []
2486 if not res and hasattr(f, 'oldname'):
2487 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 " \
2488 "FROM pg_class c,pg_attribute a,pg_type t " \
2489 "WHERE c.relname=%s " \
2490 "AND a.attname=%s " \
2491 "AND c.oid=a.attrelid " \
2492 "AND a.atttypid=t.oid", (self._table, f.oldname))
2493 res_old = cr.dictfetchall()
2494 if res_old and len(res_old) == 1:
2495 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2497 res[0]['attname'] = k
2498 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2499 self._table, f.oldname, k)
2503 f_pg_type = f_pg_def['typname']
2504 f_pg_size = f_pg_def['size']
2505 f_pg_notnull = f_pg_def['attnotnull']
2506 if isinstance(f, fields.function) and not f.store and\
2507 not getattr(f, 'nodrop', False):
2508 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2509 k, f.string, self._table)
2510 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2512 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2516 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2521 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2522 ('varchar', 'text', 'TEXT', ''),
2523 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2524 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2525 ('timestamp', 'date', 'date', '::date'),
2526 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2527 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2529 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2530 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2531 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2532 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2533 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2535 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2536 self._table, k, f_pg_size, f.size)
2538 if (f_pg_type==c[0]) and (f._type==c[1]):
2539 if f_pg_type != f_obj_type:
2541 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2542 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2543 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2544 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2546 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2547 self._table, k, c[0], c[1])
2550 if f_pg_type != f_obj_type:
2554 newname = k + '_moved' + str(i)
2555 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2556 "WHERE c.relname=%s " \
2557 "AND a.attname=%s " \
2558 "AND c.oid=a.attrelid ", (self._table, newname))
2559 if not cr.fetchone()[0]:
2563 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2564 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2565 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2566 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2567 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2568 self._table, k, f_pg_type, f._type, newname)
2570 # if the field is required and hasn't got a NOT NULL constraint
2571 if f.required and f_pg_notnull == 0:
2572 # set the field to the default value if any
2573 if k in self._defaults:
2574 if callable(self._defaults[k]):
2575 default = self._defaults[k](self, cr, 1, context)
2577 default = self._defaults[k]
2579 if (default is not None):
2580 ss = self._columns[k]._symbol_set
2581 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2582 cr.execute(query, (ss[1](default),))
2583 # add the NOT NULL constraint
2586 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2588 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2591 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2592 "If you want to have it, you should update the records and execute manually:\n"\
2593 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2594 self.__schema.warn(msg, self._table, k, self._table, k)
2596 elif not f.required and f_pg_notnull == 1:
2597 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2599 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2602 indexname = '%s_%s_index' % (self._table, k)
2603 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2604 res2 = cr.dictfetchall()
2605 if not res2 and f.select:
2606 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2608 if f._type == 'text':
2609 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2610 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2611 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2612 " because there is a length limit for indexable btree values!\n"\
2613 "Use a search view instead if you simply want to make the field searchable."
2614 self.__schema.warn(msg, self._table, k, f._type)
2615 if res2 and not f.select:
2616 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2618 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2619 self.__schema.debug(msg, self._table, k, f._type)
2621 if isinstance(f, fields.many2one):
2622 ref = self.pool.get(f._obj)._table
2623 if ref != 'ir_actions':
2624 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2625 'pg_attribute as att1, pg_attribute as att2 '
2626 'WHERE con.conrelid = cl1.oid '
2627 'AND cl1.relname = %s '
2628 'AND con.confrelid = cl2.oid '
2629 'AND cl2.relname = %s '
2630 'AND array_lower(con.conkey, 1) = 1 '
2631 'AND con.conkey[1] = att1.attnum '
2632 'AND att1.attrelid = cl1.oid '
2633 'AND att1.attname = %s '
2634 'AND array_lower(con.confkey, 1) = 1 '
2635 'AND con.confkey[1] = att2.attnum '
2636 'AND att2.attrelid = cl2.oid '
2637 'AND att2.attname = %s '
2638 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2639 res2 = cr.dictfetchall()
2641 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2642 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2643 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2645 self.__schema.debug("Table '%s': column '%s': XXX",
2648 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2650 if not isinstance(f, fields.function) or f.store:
2651 # add the missing field
2652 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2653 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2654 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2655 self._table, k, get_pg_type(f)[1])
2658 if not create and k in self._defaults:
2659 if callable(self._defaults[k]):
2660 default = self._defaults[k](self, cr, 1, context)
2662 default = self._defaults[k]
2664 ss = self._columns[k]._symbol_set
2665 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2666 cr.execute(query, (ss[1](default),))
2668 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2670 if isinstance(f, fields.function):
2672 if f.store is not True:
2673 order = f.store[f.store.keys()[0]][2]
2674 todo_update_store.append((order, f, k))
2676 # and add constraints if needed
2677 if isinstance(f, fields.many2one):
2678 if not self.pool.get(f._obj):
2679 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2680 ref = self.pool.get(f._obj)._table
2681 # ref = f._obj.replace('.', '_')
2682 # ir_actions is inherited so foreign key doesn't work on it
2683 if ref != 'ir_actions':
2684 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2685 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2686 self._table, k, ref, f.ondelete)
2688 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2692 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2693 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2696 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2697 "Try to re-run: openerp-server.py --update=module\n"\
2698 "If it doesn't work, update records and execute manually:\n"\
2699 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2700 self.__logger.warn(msg, k, self._table, self._table, k)
2702 for order, f, k in todo_update_store:
2703 todo_end.append((order, self._update_store, (f, k)))
2706 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2707 create = not bool(cr.fetchone())
2709 cr.commit() # start a new transaction
2711 for (key, con, _) in self._sql_constraints:
2712 conname = '%s_%s' % (self._table, key)
2714 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2715 existing_constraints = cr.dictfetchall()
2720 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2721 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2722 self._table, conname, con),
2723 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2728 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2729 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2730 '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" % (
2736 if not existing_constraints:
2737 # constraint does not exists:
2738 sql_actions['add']['execute'] = True
2739 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2740 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2741 # constraint exists but its definition has changed:
2742 sql_actions['drop']['execute'] = True
2743 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2744 sql_actions['add']['execute'] = True
2745 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2747 # we need to add the constraint:
2748 sql_actions = [item for item in sql_actions.values()]
2749 sql_actions.sort(key=lambda x: x['order'])
2750 for sql_action in [action for action in sql_actions if action['execute']]:
2752 cr.execute(sql_action['query'])
2754 self.__schema.debug(sql_action['msg_ok'])
2756 self.__schema.warn(sql_action['msg_err'])
2760 if hasattr(self, "_sql"):
2761 for line in self._sql.split(';'):
2762 line2 = line.replace('\n', '').strip()
2767 self._parent_store_compute(cr)
2771 def __init__(self, cr):
2772 super(orm, self).__init__(cr)
2774 if not hasattr(self, '_log_access'):
2775 # if not access is not specify, it is the same value as _auto
2776 self._log_access = getattr(self, "_auto", True)
2778 self._columns = self._columns.copy()
2779 for store_field in self._columns:
2780 f = self._columns[store_field]
2781 if hasattr(f, 'digits_change'):
2783 if not isinstance(f, fields.function):
2787 if self._columns[store_field].store is True:
2788 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2790 sm = self._columns[store_field].store
2791 for object, aa in sm.items():
2793 (fnct, fields2, order, length) = aa
2795 (fnct, fields2, order) = aa
2798 raise except_orm('Error',
2799 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2800 self.pool._store_function.setdefault(object, [])
2802 for x, y, z, e, f, l in self.pool._store_function[object]:
2803 if (x==self._name) and (y==store_field) and (e==fields2):
2807 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2808 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2810 for (key, _, msg) in self._sql_constraints:
2811 self.pool._sql_error[self._table+'_'+key] = msg
2813 # Load manual fields
2815 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2817 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2818 for field in cr.dictfetchall():
2819 if field['name'] in self._columns:
2822 'string': field['field_description'],
2823 'required': bool(field['required']),
2824 'readonly': bool(field['readonly']),
2825 'domain': eval(field['domain']) if field['domain'] else None,
2826 'size': field['size'],
2827 'ondelete': field['on_delete'],
2828 'translate': (field['translate']),
2829 #'select': int(field['select_level'])
2832 if field['ttype'] == 'selection':
2833 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2834 elif field['ttype'] == 'reference':
2835 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2836 elif field['ttype'] == 'many2one':
2837 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2838 elif field['ttype'] == 'one2many':
2839 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2840 elif field['ttype'] == 'many2many':
2841 _rel1 = field['relation'].replace('.', '_')
2842 _rel2 = field['model'].replace('.', '_')
2843 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2844 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2846 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2847 self._inherits_check()
2848 self._inherits_reload()
2849 if not self._sequence:
2850 self._sequence = self._table + '_id_seq'
2851 for k in self._defaults:
2852 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,)
2853 for f in self._columns:
2854 self._columns[f].restart()
2857 # Update objects that uses this one to update their _inherits fields
2860 def _inherits_reload_src(self):
2861 for obj in self.pool.obj_pool.values():
2862 if self._name in obj._inherits:
2863 obj._inherits_reload()
2865 def _inherits_reload(self):
2867 for table in self._inherits:
2868 res.update(self.pool.get(table)._inherit_fields)
2869 for col in self.pool.get(table)._columns.keys():
2870 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2871 for col in self.pool.get(table)._inherit_fields.keys():
2872 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2873 self._inherit_fields = res
2874 self._inherits_reload_src()
2876 def _inherits_check(self):
2877 for table, field_name in self._inherits.items():
2878 if field_name not in self._columns:
2879 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2880 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2881 required=True, ondelete="cascade")
2882 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2883 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))
2884 self._columns[field_name].required = True
2885 self._columns[field_name].ondelete = "cascade"
2887 #def __getattr__(self, name):
2889 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2890 # (though inherits doesn't use Python inheritance).
2891 # Handles translating between local ids and remote ids.
2892 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2893 # when you have inherits.
2895 # for model, field in self._inherits.iteritems():
2896 # proxy = self.pool.get(model)
2897 # if hasattr(proxy, name):
2898 # attribute = getattr(proxy, name)
2899 # if not hasattr(attribute, '__call__'):
2903 # return super(orm, self).__getattr__(name)
2905 # def _proxy(cr, uid, ids, *args, **kwargs):
2906 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2907 # lst = [obj[field].id for obj in objects if obj[field]]
2908 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2913 def fields_get(self, cr, user, fields=None, context=None):
2915 Get the description of list of fields
2917 :param cr: database cursor
2918 :param user: current user id
2919 :param fields: list of fields
2920 :param context: context arguments, like lang, time zone
2921 :return: dictionary of field dictionaries, each one describing a field of the business object
2922 :raise AccessError: * if user has no create/write rights on the requested object
2925 ira = self.pool.get('ir.model.access')
2926 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2927 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2928 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2930 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2933 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2935 fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
2936 if isinstance(ids, (int, long)):
2940 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
2941 result = self._read_flat(cr, user, select, fields, context, load)
2944 for key, v in r.items():
2948 if isinstance(ids, (int, long, dict)):
2949 return result and result[0] or False
2952 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2957 if fields_to_read == None:
2958 fields_to_read = self._columns.keys()
2960 # Construct a clause for the security rules.
2961 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2962 # or will at least contain self._table.
2963 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2965 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2966 fields_pre = [f for f in fields_to_read if
2967 f == self.CONCURRENCY_CHECK_FIELD
2968 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2969 ] + self._inherits.values()
2973 def convert_field(f):
2974 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2975 if f in ('create_date', 'write_date'):
2976 return "date_trunc('second', %s) as %s" % (f_qual, f)
2977 if f == self.CONCURRENCY_CHECK_FIELD:
2978 if self._log_access:
2979 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2980 return "now()::timestamp AS %s" % (f,)
2981 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2982 return 'length(%s) as "%s"' % (f_qual, f)
2985 fields_pre2 = map(convert_field, fields_pre)
2986 order_by = self._parent_order or self._order
2987 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2988 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2990 query += " AND " + (' OR '.join(rule_clause))
2991 query += " ORDER BY " + order_by
2992 for sub_ids in cr.split_for_in_conditions(ids):
2994 cr.execute(query, [tuple(sub_ids)] + rule_params)
2995 if cr.rowcount != len(sub_ids):
2996 raise except_orm(_('AccessError'),
2997 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
2998 % (self._description,))
3000 cr.execute(query, (tuple(sub_ids),))
3001 res.extend(cr.dictfetchall())
3003 res = map(lambda x: {'id': x}, ids)
3005 for f in fields_pre:
3006 if f == self.CONCURRENCY_CHECK_FIELD:
3008 if self._columns[f].translate:
3009 ids = [x['id'] for x in res]
3010 #TODO: optimize out of this loop
3011 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3013 r[f] = res_trans.get(r['id'], False) or r[f]
3015 for table in self._inherits:
3016 col = self._inherits[table]
3017 cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
3020 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3028 if not record[col]: # if the record is deleted from _inherits table?
3030 record.update(res3[record[col]])
3031 if col not in fields_to_read:
3034 # all fields which need to be post-processed by a simple function (symbol_get)
3035 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3038 for f in fields_post:
3039 r[f] = self._columns[f]._symbol_get(r[f])
3040 ids = [x['id'] for x in res]
3042 # all non inherited fields for which the attribute whose name is in load is False
3043 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3045 # Compute POST fields
3047 for f in fields_post:
3048 todo.setdefault(self._columns[f]._multi, [])
3049 todo[self._columns[f]._multi].append(f)
3050 for key, val in todo.items():
3052 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3055 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3056 multi_fields = res2.get(record['id'],{})
3058 record[pos] = multi_fields.get(pos,[])
3061 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3064 record[f] = res2[record['id']]
3069 for field in vals.copy():
3071 if field in self._columns:
3072 fobj = self._columns[field]
3079 for group in groups:
3080 module = group.split(".")[0]
3081 grp = group.split(".")[1]
3082 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", \
3083 (grp, module, 'res.groups', user))
3084 readonly = cr.fetchall()
3085 if readonly[0][0] >= 1:
3088 elif readonly[0][0] == 0:
3094 if type(vals[field]) == type([]):
3096 elif type(vals[field]) == type(0.0):
3098 elif type(vals[field]) == type(''):
3099 vals[field] = '=No Permission='
3104 def perm_read(self, cr, user, ids, context=None, details=True):
3106 Returns some metadata about the given records.
3108 :param details: if True, \*_uid fields are replaced with the name of the user
3109 :return: list of ownership dictionaries for each requested record
3110 :rtype: list of dictionaries with the following keys:
3113 * create_uid: user who created the record
3114 * create_date: date when the record was created
3115 * write_uid: last user who changed the record
3116 * write_date: date of the last change to the record
3117 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3124 uniq = isinstance(ids, (int, long))
3128 if self._log_access:
3129 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3130 quoted_table = '"%s"' % self._table
3131 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3132 query = '''SELECT %s, __imd.module, __imd.name
3133 FROM %s LEFT JOIN ir_model_data __imd
3134 ON (__imd.model = %%s and __imd.res_id = %s.id)
3135 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3136 cr.execute(query, (self._name, tuple(ids)))
3137 res = cr.dictfetchall()
3140 r[key] = r[key] or False
3141 if details and key in ('write_uid', 'create_uid') and r[key]:
3143 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3145 pass # Leave the numeric uid there
3146 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3147 del r['name'], r['module']
3152 def _check_concurrency(self, cr, ids, context):
3155 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3157 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3158 for sub_ids in cr.split_for_in_conditions(ids):
3161 id_ref = "%s,%s" % (self._name, id)
3162 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3164 ids_to_check.extend([id, update_date])
3165 if not ids_to_check:
3167 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3170 # mention the first one only to keep the error message readable
3171 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3173 def check_access_rule(self, cr, uid, ids, operation, context=None):
3174 """Verifies that the operation given by ``operation`` is allowed for the user
3175 according to ir.rules.
3177 :param operation: one of ``write``, ``unlink``
3178 :raise except_orm: * if current ir.rules do not permit this operation.
3179 :return: None if the operation is allowed
3181 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3183 where_clause = ' and ' + ' and '.join(where_clause)
3184 for sub_ids in cr.split_for_in_conditions(ids):
3185 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3186 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3187 [sub_ids] + where_params)
3188 if cr.rowcount != len(sub_ids):
3189 raise except_orm(_('AccessError'),
3190 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3191 % (operation, self._description))
3193 def unlink(self, cr, uid, ids, context=None):
3195 Delete records with given ids
3197 :param cr: database cursor
3198 :param uid: current user id
3199 :param ids: id or list of ids
3200 :param context: (optional) context arguments, like lang, time zone
3202 :raise AccessError: * if user has no unlink rights on the requested object
3203 * if user tries to bypass access rules for unlink on the requested object
3204 :raise UserError: if the record is default property for other records
3209 if isinstance(ids, (int, long)):
3212 result_store = self._store_get_values(cr, uid, ids, None, context)
3214 self._check_concurrency(cr, ids, context)
3216 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3218 properties = self.pool.get('ir.property')
3219 domain = [('res_id', '=', False),
3220 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3222 if properties.search(cr, uid, domain, context=context):
3223 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3225 wf_service = netsvc.LocalService("workflow")
3227 wf_service.trg_delete(uid, self._name, oid, cr)
3230 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3231 pool_model_data = self.pool.get('ir.model.data')
3232 pool_ir_values = self.pool.get('ir.values')
3233 for sub_ids in cr.split_for_in_conditions(ids):
3234 cr.execute('delete from ' + self._table + ' ' \
3235 'where id IN %s', (sub_ids,))
3237 # Removing the ir_model_data reference if the record being deleted is a record created by xml/csv file,
3238 # as these are not connected with real database foreign keys, and would be dangling references.
3239 # Step 1. Calling unlink of ir_model_data only for the affected IDS.
3240 referenced_ids = pool_model_data.search(cr, uid, [('res_id','in',list(sub_ids)),('model','=',self._name)], context=context)
3241 # Step 2. Marching towards the real deletion of referenced records
3242 pool_model_data.unlink(cr, uid, referenced_ids, context=context)
3244 # For the same reason, removing the record relevant to ir_values
3245 ir_value_ids = pool_ir_values.search(cr, uid,
3246 ['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
3249 pool_ir_values.unlink(cr, uid, ir_value_ids, context=context)
3251 for order, object, store_ids, fields in result_store:
3252 if object != self._name:
3253 obj = self.pool.get(object)
3254 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3255 rids = map(lambda x: x[0], cr.fetchall())
3257 obj._store_set_values(cr, uid, rids, fields, context)
3264 def write(self, cr, user, ids, vals, context=None):
3266 Update records with given ids with the given field values
3268 :param cr: database cursor
3269 :param user: current user id
3271 :param ids: object id or list of object ids to update according to **vals**
3272 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3273 :type vals: dictionary
3274 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3275 :type context: dictionary
3277 :raise AccessError: * if user has no write rights on the requested object
3278 * if user tries to bypass access rules for write on the requested object
3279 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3280 :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)
3282 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3284 + For a many2many field, a list of tuples is expected.
3285 Here is the list of tuple that are accepted, with the corresponding semantics ::
3287 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3288 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3289 (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)
3290 (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)
3291 (4, ID) link to existing record with id = ID (adds a relationship)
3292 (5) unlink all (like using (3,ID) for all linked records)
3293 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3296 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3298 + For a one2many field, a lits of tuples is expected.
3299 Here is the list of tuple that are accepted, with the corresponding semantics ::
3301 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3302 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3303 (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)
3306 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3308 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3309 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3313 for field in vals.copy():
3315 if field in self._columns:
3316 fobj = self._columns[field]
3317 elif field in self._inherit_fields:
3318 fobj = self._inherit_fields[field][2]
3325 for group in groups:
3326 module = group.split(".")[0]
3327 grp = group.split(".")[1]
3328 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", \
3329 (grp, module, 'res.groups', user))
3330 readonly = cr.fetchall()
3331 if readonly[0][0] >= 1:
3334 elif readonly[0][0] == 0:
3346 if isinstance(ids, (int, long)):
3349 self._check_concurrency(cr, ids, context)
3350 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3352 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3354 # No direct update of parent_left/right
3355 vals.pop('parent_left', None)
3356 vals.pop('parent_right', None)
3358 parents_changed = []
3359 if self._parent_store and (self._parent_name in vals):
3360 # The parent_left/right computation may take up to
3361 # 5 seconds. No need to recompute the values if the
3362 # parent is the same. Get the current value of the parent
3363 parent_val = vals[self._parent_name]
3365 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3366 (self._table, self._parent_name, self._parent_name)
3367 cr.execute(query, (tuple(ids), parent_val))
3369 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3370 (self._table, self._parent_name)
3371 cr.execute(query, (tuple(ids),))
3372 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3379 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3381 if field in self._columns:
3382 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3383 if (not totranslate) or not self._columns[field].translate:
3384 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3385 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3386 direct.append(field)
3388 upd_todo.append(field)
3390 updend.append(field)
3391 if field in self._columns \
3392 and hasattr(self._columns[field], 'selection') \
3394 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3396 if self._log_access:
3397 upd0.append('write_uid=%s')
3398 upd0.append('write_date=now()')
3402 self.check_access_rule(cr, user, ids, 'write', context=context)
3403 for sub_ids in cr.split_for_in_conditions(ids):
3404 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3405 'where id IN %s', upd1 + [sub_ids])
3406 if cr.rowcount != len(sub_ids):
3407 raise except_orm(_('AccessError'),
3408 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3413 if self._columns[f].translate:
3414 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3417 # Inserting value to DB
3418 self.write(cr, user, ids, {f: vals[f]})
3419 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3422 # call the 'set' method of fields which are not classic_write
3423 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3425 # default element in context must be removed when call a one2many or many2many
3426 rel_context = context.copy()
3427 for c in context.items():
3428 if c[0].startswith('default_'):
3429 del rel_context[c[0]]
3431 for field in upd_todo:
3433 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3435 for table in self._inherits:
3436 col = self._inherits[table]
3438 for sub_ids in cr.split_for_in_conditions(ids):
3439 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3440 'where id IN %s', (sub_ids,))
3441 nids.extend([x[0] for x in cr.fetchall()])
3445 if self._inherit_fields[val][0] == table:
3448 self.pool.get(table).write(cr, user, nids, v, context)
3450 self._validate(cr, user, ids, context)
3452 # TODO: use _order to set dest at the right position and not first node of parent
3453 # We can't defer parent_store computation because the stored function
3454 # fields that are computer may refer (directly or indirectly) to
3455 # parent_left/right (via a child_of domain)
3458 self.pool._init_parent[self._name] = True
3460 order = self._parent_order or self._order
3461 parent_val = vals[self._parent_name]
3463 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3465 clause, params = '%s IS NULL' % (self._parent_name,), ()
3467 for id in parents_changed:
3468 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3469 pleft, pright = cr.fetchone()
3470 distance = pright - pleft + 1
3472 # Positions of current siblings, to locate proper insertion point;
3473 # this can _not_ be fetched outside the loop, as it needs to be refreshed
3474 # after each update, in case several nodes are sequentially inserted one
3475 # next to the other (i.e computed incrementally)
3476 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3477 parents = cr.fetchall()
3479 # Find Position of the element
3481 for (parent_pright, parent_id) in parents:
3484 position = parent_pright + 1
3486 # It's the first node of the parent
3491 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3492 position = cr.fetchone()[0] + 1
3494 if pleft < position <= pright:
3495 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3497 if pleft < position:
3498 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3499 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3500 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))
3502 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3503 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3504 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))
3506 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3510 for order, object, ids_to_update, fields_to_recompute in result:
3511 key = (object, tuple(fields_to_recompute))
3512 done.setdefault(key, {})
3513 # avoid to do several times the same computation
3515 for id in ids_to_update:
3516 if id not in done[key]:
3517 done[key][id] = True
3519 self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
3521 wf_service = netsvc.LocalService("workflow")
3523 wf_service.trg_write(user, self._name, id, cr)
3527 # TODO: Should set perm to user.xxx
3529 def create(self, cr, user, vals, context=None):
3531 Create new record with specified value
3533 :param cr: database cursor
3534 :param user: current user id
3536 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3537 :type vals: dictionary
3538 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3539 :type context: dictionary
3540 :return: id of new record created
3541 :raise AccessError: * if user has no create rights on the requested object
3542 * if user tries to bypass access rules for create on the requested object
3543 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3544 :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)
3546 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3547 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3553 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3555 vals = self._add_missing_default_values(cr, user, vals, context)
3558 for v in self._inherits:
3559 if self._inherits[v] not in vals:
3562 tocreate[v] = {'id': vals[self._inherits[v]]}
3563 (upd0, upd1, upd2) = ('', '', [])
3565 for v in vals.keys():
3566 if v in self._inherit_fields:
3567 (table, col, col_detail) = self._inherit_fields[v]
3568 tocreate[table][v] = vals[v]
3571 if (v not in self._inherit_fields) and (v not in self._columns):
3574 # Try-except added to filter the creation of those records whose filds are readonly.
3575 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3577 cr.execute("SELECT nextval('"+self._sequence+"')")
3579 raise except_orm(_('UserError'),
3580 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3582 id_new = cr.fetchone()[0]
3583 for table in tocreate:
3584 if self._inherits[table] in vals:
3585 del vals[self._inherits[table]]
3587 record_id = tocreate[table].pop('id', None)
3589 if record_id is None or not record_id:
3590 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3592 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3594 upd0 += ',' + self._inherits[table]
3596 upd2.append(record_id)
3598 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3599 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3601 for bool_field in bool_fields:
3602 if bool_field not in vals:
3603 vals[bool_field] = False
3605 for field in vals.copy():
3607 if field in self._columns:
3608 fobj = self._columns[field]
3610 fobj = self._inherit_fields[field][2]
3616 for group in groups:
3617 module = group.split(".")[0]
3618 grp = group.split(".")[1]
3619 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" % \
3620 (grp, module, 'res.groups', user))
3621 readonly = cr.fetchall()
3622 if readonly[0][0] >= 1:
3625 elif readonly[0][0] == 0:
3633 if self._columns[field]._classic_write:
3634 upd0 = upd0 + ',"' + field + '"'
3635 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3636 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3638 if not isinstance(self._columns[field], fields.related):
3639 upd_todo.append(field)
3640 if field in self._columns \
3641 and hasattr(self._columns[field], 'selection') \
3643 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3644 if self._log_access:
3645 upd0 += ',create_uid,create_date'
3648 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3649 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3650 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3652 if self._parent_store and not context.get('defer_parent_store_computation'):
3654 self.pool._init_parent[self._name] = True
3656 parent = vals.get(self._parent_name, False)
3658 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3660 result_p = cr.fetchall()
3661 for (pleft,) in result_p:
3666 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3667 pleft_old = cr.fetchone()[0]
3670 cr.execute('select max(parent_right) from '+self._table)
3671 pleft = cr.fetchone()[0] or 0
3672 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3673 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3674 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3676 # default element in context must be remove when call a one2many or many2many
3677 rel_context = context.copy()
3678 for c in context.items():
3679 if c[0].startswith('default_'):
3680 del rel_context[c[0]]
3683 for field in upd_todo:
3684 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3685 self._validate(cr, user, [id_new], context)
3687 if not context.get('no_store_function', False):
3688 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3691 for order, object, ids, fields2 in result:
3692 if not (object, ids, fields2) in done:
3693 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3694 done.append((object, ids, fields2))
3696 if self._log_create and not (context and context.get('no_store_function', False)):
3697 message = self._description + \
3699 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3700 "' " + _("created.")
3701 self.log(cr, user, id_new, message, True, context=context)
3702 wf_service = netsvc.LocalService("workflow")
3703 wf_service.trg_create(user, self._name, id_new, cr)
3706 def _store_get_values(self, cr, uid, ids, fields, context):
3707 """Returns an ordered list of fields.functions to call due to
3708 an update operation on ``fields`` of records with ``ids``,
3709 obtained by calling the 'store' functions of these fields,
3710 as setup by their 'store' attribute.
3712 :return: [(priority, model_name, [record_ids,], [function_fields,])]
3714 # FIXME: rewrite, cleanup, use real variable names
3715 # e.g.: http://pastie.org/1222060
3717 fncts = self.pool._store_function.get(self._name, [])
3718 for fnct in range(len(fncts)):
3723 for f in (fields or []):
3724 if f in fncts[fnct][3]:
3730 result.setdefault(fncts[fnct][0], {})
3732 # uid == 1 for accessing objects having rules defined on store fields
3733 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3734 for id in filter(None, ids2):
3735 result[fncts[fnct][0]].setdefault(id, [])
3736 result[fncts[fnct][0]][id].append(fnct)
3738 for object in result:
3740 for id, fnct in result[object].items():
3741 k2.setdefault(tuple(fnct), [])
3742 k2[tuple(fnct)].append(id)
3743 for fnct, id in k2.items():
3744 dict.setdefault(fncts[fnct[0]][4], [])
3745 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3753 def _store_set_values(self, cr, uid, ids, fields, context):
3754 """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
3755 respecting ``multi`` attributes), and stores the resulting values in the database directly."""
3760 if self._log_access:
3761 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3765 field_dict.setdefault(r[0], [])
3766 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3767 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3768 for i in self.pool._store_function.get(self._name, []):
3770 up_write_date = write_date + datetime.timedelta(hours=i[5])
3771 if datetime.datetime.now() < up_write_date:
3773 field_dict[r[0]].append(i[1])
3779 if self._columns[f]._multi not in keys:
3780 keys.append(self._columns[f]._multi)
3781 todo.setdefault(self._columns[f]._multi, [])
3782 todo[self._columns[f]._multi].append(f)
3786 # uid == 1 for accessing objects having rules defined on store fields
3787 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3788 for id, value in result.items():
3790 for f in value.keys():
3791 if f in field_dict[id]:
3798 if self._columns[v]._type in ('many2one', 'one2one'):
3800 value[v] = value[v][0]
3803 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3804 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3807 cr.execute('update "' + self._table + '" set ' + \
3808 ','.join(upd0) + ' where id = %s', upd1)
3812 # uid == 1 for accessing objects having rules defined on store fields
3813 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3814 for r in result.keys():
3816 if r in field_dict.keys():
3817 if f in field_dict[r]:
3819 for id, value in result.items():
3820 if self._columns[f]._type in ('many2one', 'one2one'):
3825 cr.execute('update "' + self._table + '" set ' + \
3826 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3832 def perm_write(self, cr, user, ids, fields, context=None):
3833 raise NotImplementedError(_('This method does not exist anymore'))
3835 # TODO: ameliorer avec NULL
3836 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3837 """Computes the WHERE clause needed to implement an OpenERP domain.
3838 :param domain: the domain to compute
3840 :param active_test: whether the default filtering of records with ``active``
3841 field set to ``False`` should be applied.
3842 :return: the query expressing the given domain as provided in domain
3843 :rtype: osv.query.Query
3848 # if the object has a field named 'active', filter out all inactive
3849 # records unless they were explicitely asked for
3850 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3852 active_in_args = False
3854 if a[0] == 'active':
3855 active_in_args = True
3856 if not active_in_args:
3857 domain.insert(0, ('active', '=', 1))
3859 domain = [('active', '=', 1)]
3863 e = expression.expression(domain)
3864 e.parse(cr, user, self, context)
3865 tables = e.get_tables()
3866 where_clause, where_params = e.to_sql()
3867 where_clause = where_clause and [where_clause] or []
3869 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3871 return Query(tables, where_clause, where_params)
3873 def _check_qorder(self, word):
3874 if not regex_order.match(word):
3875 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)'))
3878 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3879 """Add what's missing in ``query`` to implement all appropriate ir.rules
3880 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3882 :param query: the current query object
3884 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3886 if parent_model and child_object:
3887 # as inherited rules are being applied, we need to add the missing JOIN
3888 # to reach the parent table (if it was not JOINed yet in the query)
3889 child_object._inherits_join_add(parent_model, query)
3890 query.where_clause += added_clause
3891 query.where_clause_params += added_params
3892 for table in added_tables:
3893 if table not in query.tables:
3894 query.tables.append(table)
3898 # apply main rules on the object
3899 rule_obj = self.pool.get('ir.rule')
3900 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3902 # apply ir.rules from the parents (through _inherits)
3903 for inherited_model in self._inherits:
3904 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3905 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3907 def _generate_m2o_order_by(self, order_field, query):
3909 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3910 either native m2o fields or function/related fields that are stored, including
3911 intermediate JOINs for inheritance if required.
3913 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3915 if order_field not in self._columns and order_field in self._inherit_fields:
3916 # also add missing joins for reaching the table containing the m2o field
3917 qualified_field = self._inherits_join_calc(order_field, query)
3918 order_field_column = self._inherit_fields[order_field][2]
3920 qualified_field = '"%s"."%s"' % (self._table, order_field)
3921 order_field_column = self._columns[order_field]
3923 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3924 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
3925 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
3926 "to be used as ordering fields! Ignoring sorting for %s.%s",
3927 self._name, order_field)
3930 # figure out the applicable order_by for the m2o
3931 dest_model = self.pool.get(order_field_column._obj)
3932 m2o_order = dest_model._order
3933 if not regex_order.match(m2o_order):
3934 # _order is complex, can't use it here, so we default to _rec_name
3935 m2o_order = dest_model._rec_name
3937 # extract the field names, to be able to qualify them and add desc/asc
3939 for order_part in m2o_order.split(","):
3940 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
3941 m2o_order = m2o_order_list
3943 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3944 # as we don't want to exclude results that have NULL values for the m2o
3945 src_table, src_field = qualified_field.replace('"','').split('.', 1)
3946 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3947 qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
3948 return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
3951 def _generate_order_by(self, order_spec, query):
3953 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
3954 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
3956 :raise" except_orm in case order_spec is malformed
3958 order_by_clause = self._order
3960 order_by_elements = []
3961 self._check_qorder(order_spec)
3962 for order_part in order_spec.split(','):
3963 order_split = order_part.strip().split(' ')
3964 order_field = order_split[0].strip()
3965 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
3967 if order_field == 'id':
3968 order_by_clause = '"%s"."%s"' % (self._table, order_field)
3969 elif order_field in self._columns:
3970 order_column = self._columns[order_field]
3971 if order_column._classic_read:
3972 inner_clause = '"%s"."%s"' % (self._table, order_field)
3973 elif order_column._type == 'many2one':
3974 inner_clause = self._generate_m2o_order_by(order_field, query)
3976 continue # ignore non-readable or "non-joinable" fields
3977 elif order_field in self._inherit_fields:
3978 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
3979 order_column = parent_obj._columns[order_field]
3980 if order_column._classic_read:
3981 inner_clause = self._inherits_join_calc(order_field, query)
3982 elif order_column._type == 'many2one':
3983 inner_clause = self._generate_m2o_order_by(order_field, query)
3985 continue # ignore non-readable or "non-joinable" fields
3987 if isinstance(inner_clause, list):
3988 for clause in inner_clause:
3989 order_by_elements.append("%s %s" % (clause, order_direction))
3991 order_by_elements.append("%s %s" % (inner_clause, order_direction))
3992 if order_by_elements:
3993 order_by_clause = ",".join(order_by_elements)
3995 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
3997 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
3999 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4000 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4001 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4002 This is ok at the security level because this method is private and not callable through XML-RPC.
4004 :param access_rights_uid: optional user ID to use when checking access rights
4005 (not for ir.rules, this is only for ir.model.access)
4009 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4011 query = self._where_calc(cr, user, args, context=context)
4012 self._apply_ir_rules(cr, user, query, 'read', context=context)
4013 order_by = self._generate_order_by(order, query)
4014 from_clause, where_clause, where_clause_params = query.get_sql()
4016 limit_str = limit and ' limit %d' % limit or ''
4017 offset_str = offset and ' offset %d' % offset or ''
4018 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4021 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4024 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4026 return [x[0] for x in res]
4028 # returns the different values ever entered for one field
4029 # this is used, for example, in the client when the user hits enter on
4031 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4034 if field in self._inherit_fields:
4035 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4037 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4039 def copy_data(self, cr, uid, id, default=None, context=None):
4041 Copy given record's data with all its fields values
4043 :param cr: database cursor
4044 :param user: current user id
4045 :param id: id of the record to copy
4046 :param default: field values to override in the original values of the copied record
4047 :type default: dictionary
4048 :param context: context arguments, like lang, time zone
4049 :type context: dictionary
4050 :return: dictionary containing all the field values
4056 # avoid recursion through already copied records in case of circular relationship
4057 seen_map = context.setdefault('__copy_data_seen',{})
4058 if id in seen_map.setdefault(self._name,[]):
4060 seen_map[self._name].append(id)
4064 if 'state' not in default:
4065 if 'state' in self._defaults:
4066 if callable(self._defaults['state']):
4067 default['state'] = self._defaults['state'](self, cr, uid, context)
4069 default['state'] = self._defaults['state']
4071 context_wo_lang = context.copy()
4072 if 'lang' in context:
4073 del context_wo_lang['lang']
4074 data = self.read(cr, uid, [id,], context=context_wo_lang)
4078 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4080 fields = self.fields_get(cr, uid, context=context)
4082 ftype = fields[f]['type']
4084 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4088 data[f] = default[f]
4089 elif 'function' in fields[f]:
4091 elif ftype == 'many2one':
4093 data[f] = data[f] and data[f][0]
4096 elif ftype in ('one2many', 'one2one'):
4098 rel = self.pool.get(fields[f]['relation'])
4100 # duplicate following the order of the ids
4101 # because we'll rely on it later for copying
4102 # translations in copy_translation()!
4104 for rel_id in data[f]:
4105 # the lines are first duplicated using the wrong (old)
4106 # parent but then are reassigned to the correct one thanks
4107 # to the (0, 0, ...)
4108 d = rel.copy_data(cr, uid, rel_id, context=context)
4110 res.append((0, 0, d))
4112 elif ftype == 'many2many':
4113 data[f] = [(6, 0, data[f])]
4117 # make sure we don't break the current parent_store structure and
4118 # force a clean recompute!
4119 for parent_column in ['parent_left', 'parent_right']:
4120 data.pop(parent_column, None)
4122 for v in self._inherits:
4123 del data[self._inherits[v]]
4126 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4130 # avoid recursion through already copied records in case of circular relationship
4131 seen_map = context.setdefault('__copy_translations_seen',{})
4132 if old_id in seen_map.setdefault(self._name,[]):
4134 seen_map[self._name].append(old_id)
4136 trans_obj = self.pool.get('ir.translation')
4137 fields = self.fields_get(cr, uid, context=context)
4139 translation_records = []
4140 for field_name, field_def in fields.items():
4141 # we must recursively copy the translations for o2o and o2m
4142 if field_def['type'] in ('one2one', 'one2many'):
4143 target_obj = self.pool.get(field_def['relation'])
4144 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4145 # here we rely on the order of the ids to match the translations
4146 # as foreseen in copy_data()
4147 old_children = sorted(old_record[field_name])
4148 new_children = sorted(new_record[field_name])
4149 for (old_child, new_child) in zip(old_children, new_children):
4150 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4151 # and for translatable fields we keep them for copy
4152 elif field_def.get('translate'):
4154 if field_name in self._columns:
4155 trans_name = self._name + "," + field_name
4156 elif field_name in self._inherit_fields:
4157 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4159 trans_ids = trans_obj.search(cr, uid, [
4160 ('name', '=', trans_name),
4161 ('res_id', '=', old_id)
4163 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4165 for record in translation_records:
4167 record['res_id'] = new_id
4168 trans_obj.create(cr, uid, record, context=context)
4171 def copy(self, cr, uid, id, default=None, context=None):
4173 Duplicate record with given id updating it with default values
4175 :param cr: database cursor
4176 :param uid: current user id
4177 :param id: id of the record to copy
4178 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4179 :type default: dictionary
4180 :param context: context arguments, like lang, time zone
4181 :type context: dictionary
4187 context = context.copy()
4188 data = self.copy_data(cr, uid, id, default, context)
4189 new_id = self.create(cr, uid, data, context)
4190 self.copy_translations(cr, uid, id, new_id, context)
4193 def exists(self, cr, uid, ids, context=None):
4194 if type(ids) in (int, long):
4196 query = 'SELECT count(1) FROM "%s"' % (self._table)
4197 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4198 return cr.fetchone()[0] == len(ids)
4200 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4201 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4202 self._name, DeprecationWarning, stacklevel=3)
4203 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4204 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4205 return self._check_recursion(cr, uid, ids, context, parent)
4207 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4209 Verifies that there is no loop in a hierarchical structure of records,
4210 by following the parent relationship using the **parent** field until a loop
4211 is detected or until a top-level record is found.
4213 :param cr: database cursor
4214 :param uid: current user id
4215 :param ids: list of ids of records to check
4216 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4217 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4221 parent = self._parent_name
4223 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4226 for i in range(0, len(ids), cr.IN_MAX):
4227 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4228 cr.execute(query, (tuple(sub_ids_parent),))
4229 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4230 ids_parent = ids_parent2
4231 for i in ids_parent:
4236 def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
4237 """Find out the XML ID(s) of any database record.
4239 **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
4241 :return: map of ids to the list of their fully qualified XML IDs
4242 (empty list when there's none).
4244 model_data_obj = self.pool.get('ir.model.data')
4245 data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
4246 data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
4249 # can't use dict.fromkeys() as the list would be shared!
4251 for record in data_results:
4252 result[record['res_id']].append('%(module)s.%(name)s' % record)
4255 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4256 """Find out the XML ID of any database record, if there
4257 is one. This method works as a possible implementation
4258 for a function field, to be able to add it to any
4259 model object easily, referencing it as ``osv.osv.get_xml_id``.
4261 When multiple XML IDs exist for a record, only one
4262 of them is returned (randomly).
4264 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4266 :return: map of ids to their fully qualified XML ID,
4267 defaulting to an empty string when there's none
4268 (to be usable as a function field).
4270 results = self._get_xml_ids(cr, uid, ids)
4271 for k, v in results.items():
4278 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: