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 'translate': (f.translate and 1) or 0,
462 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
465 # When its a custom field,it does not contain f.select
466 if context.get('field_state', 'base') == 'manual':
467 if context.get('field_name', '') == k:
468 vals['select_level'] = context.get('select', '0')
469 #setting value to let the problem NOT occur next time
471 vals['select_level'] = cols[k]['select_level']
474 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
475 id = cr.fetchone()[0]
477 cr.execute("""INSERT INTO ir_model_fields (
478 id, model_id, model, name, field_description, ttype,
479 relation,view_load,state,select_level,relation_field, translate
481 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
483 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
484 vals['relation'], bool(vals['view_load']), 'base',
485 vals['select_level'], vals['relation_field'], bool(vals['translate'])
487 if 'module' in context:
488 name1 = 'field_' + self._table + '_' + k
489 cr.execute("select name from ir_model_data where name=%s", (name1,))
491 name1 = name1 + "_" + str(id)
492 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
493 (name1, context['module'], 'ir.model.fields', id)
496 for key, val in vals.items():
497 if cols[k][key] != vals[key]:
498 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
500 cr.execute("""UPDATE ir_model_fields SET
501 model_id=%s, field_description=%s, ttype=%s, relation=%s,
502 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s
504 model=%s AND name=%s""", (
505 vals['model_id'], vals['field_description'], vals['ttype'],
506 vals['relation'], bool(vals['view_load']),
507 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['model'], vals['name']
512 def _auto_init(self, cr, context=None):
513 self._field_create(cr, context=context)
515 def __init__(self, cr):
516 if not self._name and not hasattr(self, '_inherit'):
517 name = type(self).__name__.split('.')[0]
518 msg = "The class %s has to have a _name attribute" % name
520 logger = netsvc.Logger()
521 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
522 raise except_orm('ValueError', msg)
524 if not self._description:
525 self._description = self._name
527 self._table = self._name.replace('.', '_')
529 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
530 """Fetch records as objects allowing to use dot notation to browse fields and relations
532 :param cr: database cursor
533 :param user: current user id
534 :param select: id or list of ids
535 :param context: context arguments, like lang, time zone
536 :rtype: object or list of objects requested
539 self._list_class = list_class or browse_record_list
541 # need to accepts ints and longs because ids coming from a method
542 # launched by button in the interface have a type long...
543 if isinstance(select, (int, long)):
544 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
545 elif isinstance(select, list):
546 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)
550 def __export_row(self, cr, uid, row, fields, context=None):
554 def check_type(field_type):
555 if field_type == 'float':
557 elif field_type == 'integer':
559 elif field_type == 'boolean':
563 def selection_field(in_field):
564 col_obj = self.pool.get(in_field.keys()[0])
565 if f[i] in col_obj._columns.keys():
566 return col_obj._columns[f[i]]
567 elif f[i] in col_obj._inherits.keys():
568 selection_field(col_obj._inherits)
573 data = map(lambda x: '', range(len(fields)))
575 for fpos in range(len(fields)):
584 model_data = self.pool.get('ir.model.data')
585 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
587 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
589 r = '%s.%s' % (d['module'], d['name'])
596 # To display external name of selection field when its exported
598 if f[i] in self._columns.keys():
599 cols = self._columns[f[i]]
600 elif f[i] in self._inherit_fields.keys():
601 cols = selection_field(self._inherits)
602 if cols and cols._type == 'selection':
603 sel_list = cols.selection
604 if r and type(sel_list) == type([]):
605 r = [x[1] for x in sel_list if r==x[0]]
606 r = r and r[0] or False
608 if f[i] in self._columns:
609 r = check_type(self._columns[f[i]]._type)
610 elif f[i] in self._inherit_fields:
611 r = check_type(self._inherit_fields[f[i]][2]._type)
612 data[fpos] = r or False
614 if isinstance(r, (browse_record_list, list)):
616 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
619 if [x for x in fields2 if x]:
623 lines2 = self.__export_row(cr, uid, row2, fields2,
626 for fpos2 in range(len(fields)):
627 if lines2 and lines2[0][fpos2]:
628 data[fpos2] = lines2[0][fpos2]
632 name_relation = self.pool.get(rr._table_name)._rec_name
633 if isinstance(rr[name_relation], browse_record):
634 rr = rr[name_relation]
635 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
636 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
637 dt += tools.ustr(rr_name or '') + ','
647 if isinstance(r, browse_record):
648 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
649 r = r and r[0] and r[0][1] or ''
650 data[fpos] = tools.ustr(r or '')
651 return [data] + lines
653 def export_data(self, cr, uid, ids, fields_to_export, context=None):
655 Export fields for selected objects
657 :param cr: database cursor
658 :param uid: current user id
659 :param ids: list of ids
660 :param fields_to_export: list of fields
661 :param context: context arguments, like lang, time zone
662 :rtype: dictionary with a *datas* matrix
664 This method is used when exporting data via client menu
669 cols = self._columns.copy()
670 for f in self._inherit_fields:
671 cols.update({f: self._inherit_fields[f][2]})
673 if x=='.id': return [x]
674 return x.replace(':id','/id').replace('.id','/.id').split('/')
675 fields_to_export = map(fsplit, fields_to_export)
676 fields_export = fields_to_export + []
680 for row in self.browse(cr, uid, ids, context):
681 datas += self.__export_row(cr, uid, row, fields_to_export, context)
682 return {'datas': datas}
684 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
686 Import given data in given module
688 :param cr: database cursor
689 :param uid: current user id
690 :param fields: list of fields
691 :param data: data to import
692 :param mode: 'init' or 'update' for record creation
693 :param current_module: module name
694 :param noupdate: flag for record creation
695 :param context: context arguments, like lang, time zone,
696 :param filename: optional file to store partial import state for recovery
699 This method is used when importing data via client menu.
701 Example of fields to import for a sale.order::
704 partner_id, (=name_search)
705 order_line/.id, (=database_id)
707 order_line/product_id/id, (=xml id)
708 order_line/price_unit,
709 order_line/product_uom_qty,
710 order_line/product_uom/id (=xml_id)
714 def _replace_field(x):
715 x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
716 return x.replace(':id','/id').split('/')
717 fields = map(_replace_field, fields)
718 logger = netsvc.Logger()
719 ir_model_data_obj = self.pool.get('ir.model.data')
721 # mode: id (XML id) or .id (database id) or False for name_get
722 def _get_id(model_name, id, current_module=False, mode='id'):
725 obj_model = self.pool.get(model_name)
726 ids = obj_model.search(cr, uid, [('id', '=', int(id))], context=context)
728 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
731 module, xml_id = id.rsplit('.', 1)
733 module, xml_id = current_module, id
734 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
735 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'], context=context)
736 if not ir_model_data:
737 raise ValueError('No references to %s.%s' % (module, xml_id))
738 id = ir_model_data[0]['res_id']
740 obj_model = self.pool.get(model_name)
741 ids = obj_model.name_search(cr, uid, id, operator='=', context=context)
743 raise ValueError('No record found for %s' % (id,))
748 # datas: a list of records, each record is defined by a list of values
749 # prefix: a list of prefix fields ['line_ids']
750 # position: the line to process, skip is False if it's the first line of the current record
752 # (res, position, warning, res_id) with
753 # res: the record for the next line to process (including it's one2many)
754 # position: the new position for the next line
755 # res_id: the ID of the record if it's a modification
756 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
757 line = datas[position]
765 for i in range(len(fields)):
770 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
773 if field[:len(prefix)] <> prefix:
778 # ID of the record using a XML ID
779 if field[len(prefix)]=='id':
781 data_res_id = _get_id(model_name, line[i], current_module, 'id')
782 except ValueError, e:
787 # ID of the record using a database ID
788 elif field[len(prefix)]=='.id':
789 data_res_id = _get_id(model_name, line[i], current_module, '.id')
792 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
793 if fields_def[field[len(prefix)]]['type']=='one2many':
794 if field[len(prefix)] in done:
796 done[field[len(prefix)]] = True
797 relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
798 newfd = relation_obj.fields_get( cr, uid, context=context )
802 while pos < len(datas):
803 res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
806 (newrow, pos, w2, data_res_id2, xml_id2) = res2
807 nbrmax = max(nbrmax, pos)
810 if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
812 res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
814 elif fields_def[field[len(prefix)]]['type']=='many2one':
815 relation = fields_def[field[len(prefix)]]['relation']
816 if len(field) == len(prefix)+1:
819 mode = field[len(prefix)+1]
820 res = _get_id(relation, line[i], current_module, mode)
822 elif fields_def[field[len(prefix)]]['type']=='many2many':
823 relation = fields_def[field[len(prefix)]]['relation']
824 if len(field) == len(prefix)+1:
827 mode = field[len(prefix)+1]
829 # TODO: improve this by using csv.csv_reader
831 for db_id in line[i].split(config.get('csv_internal_sep')):
832 res.append( _get_id(relation, db_id, current_module, mode) )
835 elif fields_def[field[len(prefix)]]['type'] == 'integer':
836 res = line[i] and int(line[i]) or 0
837 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
838 res = line[i].lower() not in ('0', 'false', 'off')
839 elif fields_def[field[len(prefix)]]['type'] == 'float':
840 res = line[i] and float(line[i]) or 0.0
841 elif fields_def[field[len(prefix)]]['type'] == 'selection':
842 for key, val in fields_def[field[len(prefix)]]['selection']:
843 if line[i] in [tools.ustr(key), tools.ustr(val)]:
846 if line[i] and not res:
847 logger.notifyChannel("import", netsvc.LOG_WARNING,
848 _("key '%s' not found in selection field '%s'") % \
849 (line[i], field[len(prefix)]))
850 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
854 row[field[len(prefix)]] = res or False
856 result = (row, nbrmax, warning, data_res_id, xml_id)
859 fields_def = self.fields_get(cr, uid, context=context)
861 if config.get('import_partial', False) and filename:
862 data = pickle.load(file(config.get('import_partial')))
863 original_value = data.get(filename, 0)
866 while position<len(datas):
869 (res, position, warning, res_id, xml_id) = \
870 process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
873 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
876 id = ir_model_data_obj._update(cr, uid, self._name,
877 current_module, res, mode=mode, xml_id=xml_id,
878 noupdate=noupdate, res_id=res_id, context=context)
880 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
882 if config.get('import_partial', False) and filename and (not (position%100)):
883 data = pickle.load(file(config.get('import_partial')))
884 data[filename] = position
885 pickle.dump(data, file(config.get('import_partial'), 'wb'))
886 if context.get('defer_parent_store_computation'):
887 self._parent_store_compute(cr)
890 if context.get('defer_parent_store_computation'):
891 self._parent_store_compute(cr)
892 return (position, 0, 0, 0)
894 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
896 Read records with given ids with the given fields
898 :param cr: database cursor
899 :param user: current user id
900 :param ids: id or list of the ids of the records to read
901 :param fields: optional list of field names to return (default: all fields would be returned)
902 :type fields: list (example ['field_name_1', ...])
903 :param context: optional context dictionary - it may contains keys for specifying certain options
904 like ``context_lang``, ``context_tz`` to alter the results of the call.
905 A special ``bin_size`` boolean flag may also be passed in the context to request the
906 value of all fields.binary columns to be returned as the size of the binary instead of its
907 contents. This can also be selectively overriden by passing a field-specific flag
908 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
909 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
910 :return: list of dictionaries((dictionary per record asked)) with requested field values
911 :rtype: [{‘name_of_the_field’: value, ...}, ...]
912 :raise AccessError: * if user has no read rights on the requested object
913 * if user tries to bypass access rules for read on the requested object
916 raise NotImplementedError(_('The read method is not implemented on this object !'))
918 def get_invalid_fields(self, cr, uid):
919 return list(self._invalids)
921 def _validate(self, cr, uid, ids, context=None):
922 context = context or {}
923 lng = context.get('lang', False) or 'en_US'
924 trans = self.pool.get('ir.translation')
926 for constraint in self._constraints:
927 fun, msg, fields = constraint
928 if not fun(self, cr, uid, ids):
929 # Check presence of __call__ directly instead of using
930 # callable() because it will be deprecated as of Python 3.0
931 if hasattr(msg, '__call__'):
932 tmp_msg = msg(self, cr, uid, ids, context=context)
933 if isinstance(tmp_msg, tuple):
934 tmp_msg, params = tmp_msg
935 translated_msg = tmp_msg % params
937 translated_msg = tmp_msg
939 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
941 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
943 self._invalids.update(fields)
946 raise except_orm('ValidateError', '\n'.join(error_msgs))
948 self._invalids.clear()
950 def default_get(self, cr, uid, fields_list, context=None):
952 Returns default values for the fields in fields_list.
954 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
955 :type fields_list: list
956 :param context: optional context dictionary - it may contains keys for specifying certain options
957 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
958 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
959 or override a default value for a field.
960 A special ``bin_size`` boolean flag may also be passed in the context to request the
961 value of all fields.binary columns to be returned as the size of the binary instead of its
962 contents. This can also be selectively overriden by passing a field-specific flag
963 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
964 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
965 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
967 # trigger view init hook
968 self.view_init(cr, uid, fields_list, context)
974 # get the default values for the inherited fields
975 for t in self._inherits.keys():
976 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
979 # get the default values defined in the object
980 for f in fields_list:
981 if f in self._defaults:
982 if callable(self._defaults[f]):
983 defaults[f] = self._defaults[f](self, cr, uid, context)
985 defaults[f] = self._defaults[f]
987 fld_def = ((f in self._columns) and self._columns[f]) \
988 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
991 if isinstance(fld_def, fields.property):
992 property_obj = self.pool.get('ir.property')
993 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
995 if isinstance(prop_value, (browse_record, browse_null)):
996 defaults[f] = prop_value.id
998 defaults[f] = prop_value
1000 if f not in defaults:
1003 # get the default values set by the user and override the default
1004 # values defined in the object
1005 ir_values_obj = self.pool.get('ir.values')
1006 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1007 for id, field, field_value in res:
1008 if field in fields_list:
1009 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1010 if fld_def._type in ('many2one', 'one2one'):
1011 obj = self.pool.get(fld_def._obj)
1012 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1014 if fld_def._type in ('many2many'):
1015 obj = self.pool.get(fld_def._obj)
1017 for i in range(len(field_value)):
1018 if not obj.search(cr, uid, [('id', '=',
1021 field_value2.append(field_value[i])
1022 field_value = field_value2
1023 if fld_def._type in ('one2many'):
1024 obj = self.pool.get(fld_def._obj)
1026 for i in range(len(field_value)):
1027 field_value2.append({})
1028 for field2 in field_value[i]:
1029 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1030 obj2 = self.pool.get(obj._columns[field2]._obj)
1031 if not obj2.search(cr, uid,
1032 [('id', '=', field_value[i][field2])]):
1034 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1035 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1036 if not obj2.search(cr, uid,
1037 [('id', '=', field_value[i][field2])]):
1039 # TODO add test for many2many and one2many
1040 field_value2[i][field2] = field_value[i][field2]
1041 field_value = field_value2
1042 defaults[field] = field_value
1044 # get the default values from the context
1045 for key in context or {}:
1046 if key.startswith('default_') and (key[8:] in fields_list):
1047 defaults[key[8:]] = context[key]
1051 def perm_read(self, cr, user, ids, context=None, details=True):
1052 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1054 def unlink(self, cr, uid, ids, context=None):
1055 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1057 def write(self, cr, user, ids, vals, context=None):
1058 raise NotImplementedError(_('The write method is not implemented on this object !'))
1060 def create(self, cr, user, vals, context=None):
1061 raise NotImplementedError(_('The create method is not implemented on this object !'))
1063 def fields_get_keys(self, cr, user, context=None):
1064 res = self._columns.keys()
1065 for parent in self._inherits:
1066 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1069 # returns the definition of each field in the object
1070 # the optional fields parameter can limit the result to some fields
1071 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1075 translation_obj = self.pool.get('ir.translation')
1076 for parent in self._inherits:
1077 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1079 if self._columns.keys():
1080 for f in self._columns.keys():
1081 field_col = self._columns[f]
1082 if allfields and f not in allfields:
1084 res[f] = {'type': field_col._type}
1085 # This additional attributes for M2M and function field is added
1086 # because we need to display tooltip with this additional information
1087 # when client is started in debug mode.
1088 if isinstance(field_col, fields.function):
1089 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1090 res[f]['store'] = field_col.store
1091 if isinstance(field_col.store, dict):
1092 res[f]['store'] = str(field_col.store)
1093 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1094 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1095 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1096 res[f]['func_obj'] = field_col._obj or False
1097 res[f]['func_method'] = field_col._method
1098 if isinstance(field_col, fields.many2many):
1099 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1100 res[f]['third_table'] = field_col._rel
1101 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1102 'change_default', 'translate', 'help', 'select', 'selectable'):
1103 if getattr(field_col, arg):
1104 res[f][arg] = getattr(field_col, arg)
1105 if not write_access:
1106 res[f]['readonly'] = True
1107 res[f]['states'] = {}
1108 for arg in ('digits', 'invisible', 'filters'):
1109 if getattr(field_col, arg, None):
1110 res[f][arg] = getattr(field_col, arg)
1112 if field_col.string:
1113 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1115 res[f]['string'] = res_trans
1117 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1119 res[f]['help'] = help_trans
1121 if hasattr(field_col, 'selection'):
1122 if isinstance(field_col.selection, (tuple, list)):
1123 sel = field_col.selection
1124 # translate each selection option
1126 for (key, val) in sel:
1129 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1130 sel2.append((key, val2 or val))
1132 res[f]['selection'] = sel
1134 # call the 'dynamic selection' function
1135 res[f]['selection'] = field_col.selection(self, cr, user, context)
1136 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1137 res[f]['relation'] = field_col._obj
1138 res[f]['domain'] = field_col._domain
1139 res[f]['context'] = field_col._context
1141 #TODO : read the fields from the database
1145 # filter out fields which aren't in the fields list
1146 for r in res.keys():
1147 if r not in allfields:
1152 # Overload this method if you need a window title which depends on the context
1154 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1157 def __view_look_dom(self, cr, user, node, view_id, context=None):
1165 if isinstance(s, unicode):
1166 return s.encode('utf8')
1169 # return True if node can be displayed to current user
1170 def check_group(node):
1171 if node.get('groups'):
1172 groups = node.get('groups').split(',')
1173 access_pool = self.pool.get('ir.model.access')
1174 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1176 node.set('invisible', '1')
1177 if 'attrs' in node.attrib:
1178 del(node.attrib['attrs']) #avoid making field visible later
1179 del(node.attrib['groups'])
1184 if node.tag in ('field', 'node', 'arrow'):
1185 if node.get('object'):
1190 if f.tag in ('field'):
1191 xml += etree.tostring(f, encoding="utf-8")
1193 new_xml = etree.fromstring(encode(xml))
1194 ctx = context.copy()
1195 ctx['base_model_name'] = self._name
1196 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1201 attrs = {'views': views}
1203 if node.get('name'):
1206 if node.get('name') in self._columns:
1207 column = self._columns[node.get('name')]
1209 column = self._inherit_fields[node.get('name')][2]
1214 relation = self.pool.get(column._obj)
1219 if f.tag in ('form', 'tree', 'graph'):
1221 ctx = context.copy()
1222 ctx['base_model_name'] = self._name
1223 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1224 views[str(f.tag)] = {
1228 attrs = {'views': views}
1229 if node.get('widget') and node.get('widget') == 'selection':
1230 # Prepare the cached selection list for the client. This needs to be
1231 # done even when the field is invisible to the current user, because
1232 # other events could need to change its value to any of the selectable ones
1233 # (such as on_change events, refreshes, etc.)
1235 # If domain and context are strings, we keep them for client-side, otherwise
1236 # we evaluate them server-side to consider them when generating the list of
1238 # TODO: find a way to remove this hack, by allow dynamic domains
1240 if column._domain and not isinstance(column._domain, basestring):
1241 dom = column._domain
1242 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1243 search_context = dict(context)
1244 if column._context and not isinstance(column._context, basestring):
1245 search_context.update(column._context)
1246 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1247 if (node.get('required') and not int(node.get('required'))) or not column.required:
1248 attrs['selection'].append((False, ''))
1249 fields[node.get('name')] = attrs
1251 elif node.tag in ('form', 'tree'):
1252 result = self.view_header_get(cr, user, False, node.tag, context)
1254 node.set('string', result)
1256 elif node.tag == 'calendar':
1257 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1258 if node.get(additional_field):
1259 fields[node.get(additional_field)] = {}
1261 if 'groups' in node.attrib:
1265 if ('lang' in context) and not result:
1266 if node.get('string'):
1267 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1268 if trans == node.get('string') and ('base_model_name' in context):
1269 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1270 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1271 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1273 node.set('string', trans)
1274 if node.get('confirm'):
1275 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1277 node.set('confirm', trans)
1279 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1281 node.set('sum', trans)
1284 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1285 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1289 def _disable_workflow_buttons(self, cr, user, node):
1291 # admin user can always activate workflow buttons
1294 # TODO handle the case of more than one workflow for a model or multiple
1295 # transitions with different groups and same signal
1296 usersobj = self.pool.get('res.users')
1297 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1298 for button in buttons:
1299 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1300 cr.execute("""SELECT DISTINCT t.group_id
1302 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1303 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1306 AND t.group_id is NOT NULL
1307 """, (self._name, button.get('name')))
1308 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1309 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1310 button.set('readonly', str(int(not can_click)))
1313 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1314 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1315 node = self._disable_workflow_buttons(cr, user, node)
1316 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1318 if node.tag == 'diagram':
1319 if node.getchildren()[0].tag == 'node':
1320 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1321 if node.getchildren()[1].tag == 'arrow':
1322 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1323 for key, value in node_fields.items():
1325 for key, value in arrow_fields.items():
1328 fields = self.fields_get(cr, user, fields_def.keys(), context)
1329 for field in fields_def:
1331 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1332 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1333 elif field in fields:
1334 fields[field].update(fields_def[field])
1336 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))
1337 res = cr.fetchall()[:]
1339 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1340 msg = "\n * ".join([r[0] for r in res])
1341 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1342 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1343 raise except_orm('View error', msg)
1346 def __get_default_calendar_view(self):
1347 """Generate a default calendar view (For internal use only).
1350 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1351 '<calendar string="%s"') % (self._description)
1353 if (self._date_name not in self._columns):
1355 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1356 if dt in self._columns:
1357 self._date_name = dt
1362 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1365 arch += ' date_start="%s"' % (self._date_name)
1367 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1368 if color in self._columns:
1369 arch += ' color="' + color + '"'
1372 dt_stop_flag = False
1374 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1375 if dt_stop in self._columns:
1376 arch += ' date_stop="' + dt_stop + '"'
1380 if not dt_stop_flag:
1381 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1382 if dt_delay in self._columns:
1383 arch += ' date_delay="' + dt_delay + '"'
1387 ' <field name="%s"/>\n'
1388 '</calendar>') % (self._rec_name)
1392 def __get_default_search_view(self, cr, uid, context=None):
1393 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1394 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1396 fields_to_search = set()
1397 fields = self.fields_get(cr, uid, context=context)
1398 for field in fields:
1399 if fields[field].get('select'):
1400 fields_to_search.add(field)
1401 for view in (form_view, tree_view):
1402 view_root = etree.fromstring(view['arch'])
1403 # Only care about select=1 in xpath below, because select=2 is covered
1404 # by the custom advanced search in clients
1405 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1407 tree_view_root = view_root # as provided by loop above
1408 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1409 field_group = etree.Element("group")
1410 search_view.append(field_group)
1412 for field_name in fields_to_search:
1413 field_group.append(etree.Element("field", attrib={'name': field_name}))
1415 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1418 # if view_id, view_type is not required
1420 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1422 Get the detailed composition of the requested view like fields, model, view architecture
1424 :param cr: database cursor
1425 :param user: current user id
1426 :param view_id: id of the view or None
1427 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1428 :param context: context arguments, like lang, time zone
1429 :param toolbar: true to include contextual actions
1430 :param submenu: example (portal_project module)
1431 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1432 :raise AttributeError:
1433 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1434 * if some tag other than 'position' is found in parent view
1435 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1442 if isinstance(s, unicode):
1443 return s.encode('utf8')
1446 def raise_view_error(error_msg, child_view_id):
1447 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1448 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1449 % { 'xml_id': child_view.xml_id,
1450 'parent_xml_id': view.xml_id,
1451 'model': self._name, })
1453 def _inherit_apply(src, inherit, inherit_id=None):
1454 def _find(node, node2):
1455 if node2.tag == 'xpath':
1456 res = node.xpath(node2.get('expr'))
1462 for n in node.getiterator(node2.tag):
1464 if node2.tag == 'field':
1465 # only compare field names, a field can be only once in a given view
1466 # at a given level (and for multilevel expressions, we should use xpath
1467 # inheritance spec anyway)
1468 if node2.get('name') == n.get('name'):
1472 for attr in node2.attrib:
1473 if attr == 'position':
1476 if n.get(attr) == node2.get(attr):
1483 # End: _find(node, node2)
1485 doc_dest = etree.fromstring(encode(inherit))
1486 toparse = [doc_dest]
1489 node2 = toparse.pop(0)
1490 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1492 if node2.tag == 'data':
1493 toparse += [ c for c in doc_dest ]
1495 node = _find(src, node2)
1496 if node is not None:
1498 if node2.get('position'):
1499 pos = node2.get('position')
1500 if pos == 'replace':
1501 parent = node.getparent()
1503 src = copy.deepcopy(node2[0])
1506 node.addprevious(child)
1507 node.getparent().remove(node)
1508 elif pos == 'attributes':
1509 for child in node2.getiterator('attribute'):
1510 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1512 node.set(attribute[0], attribute[1])
1514 del(node.attrib[attribute[0]])
1516 sib = node.getnext()
1520 elif pos == 'after':
1525 sib.addprevious(child)
1526 elif pos == 'before':
1527 node.addprevious(child)
1529 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1532 ' %s="%s"' % (attr, node2.get(attr))
1533 for attr in node2.attrib
1534 if attr != 'position'
1536 tag = "<%s%s>" % (node2.tag, attrs)
1537 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1539 # End: _inherit_apply(src, inherit)
1541 result = {'type': view_type, 'model': self._name}
1546 parent_view_model = None
1548 view_ref = context.get(view_type + '_view_ref', False)
1549 if view_ref and not view_id:
1551 module, view_ref = view_ref.split('.', 1)
1552 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1553 view_ref_res = cr.fetchone()
1555 view_id = view_ref_res[0]
1558 query = "SELECT arch,name,field_parent,id,type,inherit_id,model FROM ir_ui_view WHERE id=%s"
1561 query += " AND model=%s"
1562 params += (self._name,)
1563 cr.execute(query, params)
1565 cr.execute('''SELECT
1566 arch,name,field_parent,id,type,inherit_id,model
1573 ORDER BY priority''', (self._name, view_type))
1574 sql_res = cr.fetchone()
1580 view_id = ok or sql_res[3]
1582 parent_view_model = sql_res[6]
1584 # if a view was found
1586 result['type'] = sql_res[4]
1587 result['view_id'] = sql_res[3]
1588 result['arch'] = sql_res[0]
1590 def _inherit_apply_rec(result, inherit_id):
1591 # get all views which inherit from (ie modify) this view
1592 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1593 sql_inherit = cr.fetchall()
1594 for (inherit, id) in sql_inherit:
1595 result = _inherit_apply(result, inherit, id)
1596 result = _inherit_apply_rec(result, id)
1599 inherit_result = etree.fromstring(encode(result['arch']))
1600 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1602 result['name'] = sql_res[1]
1603 result['field_parent'] = sql_res[2] or False
1606 # otherwise, build some kind of default view
1607 if view_type == 'form':
1608 res = self.fields_get(cr, user, context=context)
1609 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1610 '<form string="%s">' % (self._description,)
1612 if res[x]['type'] not in ('one2many', 'many2many'):
1613 xml += '<field name="%s"/>' % (x,)
1614 if res[x]['type'] == 'text':
1618 elif view_type == 'tree':
1619 _rec_name = self._rec_name
1620 if _rec_name not in self._columns:
1621 _rec_name = self._columns.keys()[0]
1622 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1623 '<tree string="%s"><field name="%s"/></tree>' \
1624 % (self._description, self._rec_name)
1626 elif view_type == 'calendar':
1627 xml = self.__get_default_calendar_view()
1629 elif view_type == 'search':
1630 xml = self.__get_default_search_view(cr, user, context)
1633 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1634 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1635 result['arch'] = etree.fromstring(encode(xml))
1636 result['name'] = 'default'
1637 result['field_parent'] = False
1638 result['view_id'] = 0
1640 if parent_view_model != self._name:
1641 ctx = context.copy()
1642 ctx['base_model_name'] = parent_view_model
1645 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1646 result['arch'] = xarch
1647 result['fields'] = xfields
1650 if context and context.get('active_id', False):
1651 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1653 act_id = data_menu.id
1655 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1656 result['submenu'] = getattr(data_action, 'menus', False)
1660 for key in ('report_sxw_content', 'report_rml_content',
1661 'report_sxw', 'report_rml',
1662 'report_sxw_content_data', 'report_rml_content_data'):
1666 ir_values_obj = self.pool.get('ir.values')
1667 resprint = ir_values_obj.get(cr, user, 'action',
1668 'client_print_multi', [(self._name, False)], False,
1670 resaction = ir_values_obj.get(cr, user, 'action',
1671 'client_action_multi', [(self._name, False)], False,
1674 resrelate = ir_values_obj.get(cr, user, 'action',
1675 'client_action_relate', [(self._name, False)], False,
1677 resprint = map(clean, resprint)
1678 resaction = map(clean, resaction)
1679 resaction = filter(lambda x: not x.get('multi', False), resaction)
1680 resprint = filter(lambda x: not x.get('multi', False), resprint)
1681 resrelate = map(lambda x: x[2], resrelate)
1683 for x in resprint + resaction + resrelate:
1684 x['string'] = x['name']
1686 result['toolbar'] = {
1688 'action': resaction,
1693 _view_look_dom_arch = __view_look_dom_arch
1695 def search_count(self, cr, user, args, context=None):
1698 res = self.search(cr, user, args, context=context, count=True)
1699 if isinstance(res, list):
1703 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1705 Search for records based on a search domain.
1707 :param cr: database cursor
1708 :param user: current user id
1709 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1710 :param offset: optional number of results to skip in the returned values (default: 0)
1711 :param limit: optional max number of records to return (default: **None**)
1712 :param order: optional columns to sort by (default: self._order=id )
1713 :param context: optional context arguments, like lang, time zone
1714 :type context: dictionary
1715 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1716 :return: id or list of ids of records matching the criteria
1717 :rtype: integer or list of integers
1718 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1720 **Expressing a search domain (args)**
1722 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1724 * **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.
1725 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1726 The semantics of most of these operators are obvious.
1727 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1728 according to the semantics of this model (i.e following the relationship field named by
1729 ``self._parent_name``, by default ``parent_id``.
1730 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1732 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1733 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1734 Be very careful about this when you combine them the first time.
1736 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1738 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1740 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::
1742 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1745 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1747 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1749 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1750 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1751 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1753 :param access_rights_uid: optional user ID to use when checking access rights
1754 (not for ir.rules, this is only for ir.model.access)
1756 raise NotImplementedError(_('The search method is not implemented on this object !'))
1758 def name_get(self, cr, user, ids, context=None):
1761 :param cr: database cursor
1762 :param user: current user id
1764 :param ids: list of ids
1765 :param context: context arguments, like lang, time zone
1766 :type context: dictionary
1767 :return: tuples with the text representation of requested objects for to-many relationships
1774 if isinstance(ids, (int, long)):
1776 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1777 [self._rec_name], context, load='_classic_write')]
1779 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1781 Search for records and their display names according to a search domain.
1783 :param cr: database cursor
1784 :param user: current user id
1785 :param name: object name to search
1786 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1787 :param operator: operator for search criterion
1788 :param context: context arguments, like lang, time zone
1789 :type context: dictionary
1790 :param limit: optional max number of records to return
1791 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1793 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1794 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1797 return self._name_search(cr, user, name, args, operator, context, limit)
1799 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1800 # solve some access rights issues
1801 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1808 args += [(self._rec_name, operator, name)]
1809 access_rights_uid = name_get_uid or user
1810 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1811 res = self.name_get(cr, access_rights_uid, ids, context)
1814 def copy(self, cr, uid, id, default=None, context=None):
1815 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1817 def exists(self, cr, uid, id, context=None):
1818 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1820 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1823 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1825 fields = self._columns.keys() + self._inherit_fields.keys()
1826 #FIXME: collect all calls to _get_source into one SQL call.
1828 res[lang] = {'code': lang}
1830 if f in self._columns:
1831 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1833 res[lang][f] = res_trans
1835 res[lang][f] = self._columns[f].string
1836 for table in self._inherits:
1837 cols = intersect(self._inherit_fields.keys(), fields)
1838 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1841 res[lang]['code'] = lang
1842 for f in res2[lang]:
1843 res[lang][f] = res2[lang][f]
1846 def write_string(self, cr, uid, id, langs, vals, context=None):
1847 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1848 #FIXME: try to only call the translation in one SQL
1851 if field in self._columns:
1852 src = self._columns[field].string
1853 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1854 for table in self._inherits:
1855 cols = intersect(self._inherit_fields.keys(), vals)
1857 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1860 def _check_removed_columns(self, cr, log=False):
1861 raise NotImplementedError()
1863 def _add_missing_default_values(self, cr, uid, values, context=None):
1864 missing_defaults = []
1865 avoid_tables = [] # avoid overriding inherited values when parent is set
1866 for tables, parent_field in self._inherits.items():
1867 if parent_field in values:
1868 avoid_tables.append(tables)
1869 for field in self._columns.keys():
1870 if not field in values:
1871 missing_defaults.append(field)
1872 for field in self._inherit_fields.keys():
1873 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1874 missing_defaults.append(field)
1876 if len(missing_defaults):
1877 # override defaults with the provided values, never allow the other way around
1878 defaults = self.default_get(cr, uid, missing_defaults, context)
1880 if ((dv in self._columns and self._columns[dv]._type == 'many2many') \
1881 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many')) \
1882 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1883 defaults[dv] = [(6, 0, defaults[dv])]
1884 if (dv in self._columns and self._columns[dv]._type == 'one2many' \
1885 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many')) \
1886 and isinstance(defaults[dv], (list, tuple)) and defaults[dv] and isinstance(defaults[dv][0], dict):
1887 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
1888 defaults.update(values)
1892 class orm_memory(orm_template):
1894 _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']
1895 _inherit_fields = {}
1896 _max_count = config.get('osv_memory_count_limit')
1897 _max_hours = config.get('osv_memory_age_limit')
1900 def __init__(self, cr):
1901 super(orm_memory, self).__init__(cr)
1905 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1907 def _check_access(self, uid, object_id, mode):
1908 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1909 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1911 def vaccum(self, cr, uid, force=False):
1912 """Run the vaccuum cleaning system, expiring and removing old records from the
1913 virtual osv_memory tables if the "max count" or "max age" conditions are enabled
1914 and have been reached. This method can be called very often (e.g. everytime a record
1915 is created), but will only actually trigger the cleanup process once out of
1916 "_check_time" times (by default once out of 20 calls)."""
1918 if (not force) and (self.check_id % self._check_time):
1922 # Age-based expiration
1924 max = time.time() - self._max_hours * 60 * 60
1925 for k,v in self.datas.iteritems():
1926 if v['internal.date_access'] < max:
1928 self.unlink(cr, 1, tounlink)
1930 # Count-based expiration
1931 if self._max_count and len(self.datas) > self._max_count:
1932 # sort by access time to remove only the first/oldest ones in LRU fashion
1933 records = self.datas.items()
1934 records.sort(key=lambda x:x[1]['internal.date_access'])
1935 self.unlink(cr, 1, [x[0] for x in records[:len(self.datas)-self._max_count]])
1939 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1942 if not fields_to_read:
1943 fields_to_read = self._columns.keys()
1947 if isinstance(ids, (int, long)):
1951 for f in fields_to_read:
1952 record = self.datas.get(id)
1954 self._check_access(user, id, 'read')
1955 r[f] = record.get(f, False)
1956 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1959 if id in self.datas:
1960 self.datas[id]['internal.date_access'] = time.time()
1961 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1962 for f in fields_post:
1963 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1964 for record in result:
1965 record[f] = res2[record['id']]
1966 if isinstance(ids_orig, (int, long)):
1970 def write(self, cr, user, ids, vals, context=None):
1976 if self._columns[field]._classic_write:
1977 vals2[field] = vals[field]
1979 upd_todo.append(field)
1980 for object_id in ids:
1981 self._check_access(user, object_id, mode='write')
1982 self.datas[object_id].update(vals2)
1983 self.datas[object_id]['internal.date_access'] = time.time()
1984 for field in upd_todo:
1985 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
1986 self._validate(cr, user, [object_id], context)
1987 wf_service = netsvc.LocalService("workflow")
1988 wf_service.trg_write(user, self._name, object_id, cr)
1991 def create(self, cr, user, vals, context=None):
1992 self.vaccum(cr, user)
1994 id_new = self.next_id
1996 vals = self._add_missing_default_values(cr, user, vals, context)
2001 if self._columns[field]._classic_write:
2002 vals2[field] = vals[field]
2004 upd_todo.append(field)
2005 self.datas[id_new] = vals2
2006 self.datas[id_new]['internal.date_access'] = time.time()
2007 self.datas[id_new]['internal.create_uid'] = user
2009 for field in upd_todo:
2010 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2011 self._validate(cr, user, [id_new], context)
2012 if self._log_create and not (context and context.get('no_store_function', False)):
2013 message = self._description + \
2015 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2017 self.log(cr, user, id_new, message, True, context=context)
2018 wf_service = netsvc.LocalService("workflow")
2019 wf_service.trg_create(user, self._name, id_new, cr)
2022 def _where_calc(self, cr, user, args, active_test=True, context=None):
2027 # if the object has a field named 'active', filter out all inactive
2028 # records unless they were explicitely asked for
2029 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2031 active_in_args = False
2033 if a[0] == 'active':
2034 active_in_args = True
2035 if not active_in_args:
2036 args.insert(0, ('active', '=', 1))
2038 args = [('active', '=', 1)]
2041 e = expression.expression(args)
2042 e.parse(cr, user, self, context)
2046 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2050 # implicit filter on current user except for superuser
2054 args.insert(0, ('internal.create_uid', '=', user))
2056 result = self._where_calc(cr, user, args, context=context)
2058 return self.datas.keys()
2062 #Find the value of dict
2065 for id, data in self.datas.items():
2066 counter = counter + 1
2068 if limit and (counter > int(limit)):
2073 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2074 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2075 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2076 elif arg[1] in ['ilike']:
2077 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2087 def unlink(self, cr, uid, ids, context=None):
2089 self._check_access(uid, id, 'unlink')
2090 self.datas.pop(id, None)
2092 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2095 def perm_read(self, cr, user, ids, context=None, details=True):
2097 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2098 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2100 self._check_access(user, id, 'read')
2102 'create_uid': credentials,
2103 'create_date': create_date,
2105 'write_date': False,
2111 def _check_removed_columns(self, cr, log=False):
2112 # nothing to check in memory...
2115 def exists(self, cr, uid, id, context=None):
2116 return id in self.datas
2118 class orm(orm_template):
2119 _sql_constraints = []
2121 _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']
2122 __logger = logging.getLogger('orm')
2123 __schema = logging.getLogger('orm.schema')
2124 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2126 Get the list of records in list view grouped by the given ``groupby`` fields
2128 :param cr: database cursor
2129 :param uid: current user id
2130 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2131 :param fields: list of fields present in the list view specified on the object
2132 :param groupby: list of fields on which to groupby the records
2133 :type fields_list: list (example ['field_name_1', ...])
2134 :param offset: optional number of records to skip
2135 :param limit: optional max number of records to return
2136 :param context: context arguments, like lang, time zone
2137 :param order: optional ``order by`` specification, for overriding the natural
2138 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2139 (supported only for many2one fields currently)
2140 :return: list of dictionaries(one dictionary for each record) containing:
2142 * the values of fields grouped by the fields in ``groupby`` argument
2143 * __domain: list of tuples specifying the search criteria
2144 * __context: dictionary with argument like ``groupby``
2145 :rtype: [{'field_name_1': value, ...]
2146 :raise AccessError: * if user has no read rights on the requested object
2147 * if user tries to bypass access rules for read on the requested object
2150 context = context or {}
2151 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2153 fields = self._columns.keys()
2155 query = self._where_calc(cr, uid, domain, context=context)
2156 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2158 # Take care of adding join(s) if groupby is an '_inherits'ed field
2159 groupby_list = groupby
2160 qualified_groupby_field = groupby
2162 if isinstance(groupby, list):
2163 groupby = groupby[0]
2164 qualified_groupby_field = self._inherits_join_calc(groupby, query)
2167 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?)"
2168 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2169 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"
2171 fget = self.fields_get(cr, uid, fields)
2172 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2174 group_count = group_by = groupby
2176 if fget.get(groupby):
2177 if fget[groupby]['type'] in ('date', 'datetime'):
2178 flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
2179 groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
2180 qualified_groupby_field = groupby
2182 flist = qualified_groupby_field
2184 # Don't allow arbitrary values, as this would be a SQL injection vector!
2185 raise except_orm(_('Invalid group_by'),
2186 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2189 fields_pre = [f for f in float_int_fields if
2190 f == self.CONCURRENCY_CHECK_FIELD
2191 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2192 for f in fields_pre:
2193 if f not in ['id', 'sequence']:
2194 group_operator = fget[f].get('group_operator', 'sum')
2197 qualified_field = '"%s"."%s"' % (self._table, f)
2198 flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2200 gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2202 from_clause, where_clause, where_clause_params = query.get_sql()
2203 where_clause = where_clause and ' WHERE ' + where_clause
2204 limit_str = limit and ' limit %d' % limit or ''
2205 offset_str = offset and ' offset %d' % offset or ''
2206 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2208 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)
2211 for r in cr.dictfetchall():
2212 for fld, val in r.items():
2213 if val == None: r[fld] = False
2214 alldata[r['id']] = r
2217 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2218 # the IDS of records that have groupby field value = False or '' should be sorted too
2219 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2220 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2221 # 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):
2222 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2226 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2227 if not isinstance(groupby_list, (str, unicode)):
2228 if groupby or not context.get('group_by_no_leaf', False):
2229 d['__context'] = {'group_by': groupby_list[1:]}
2230 if groupby and groupby in fget:
2231 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2232 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2233 days = calendar.monthrange(dt.year, dt.month)[1]
2235 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2236 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),\
2237 (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
2238 del alldata[d['id']][groupby]
2239 d.update(alldata[d['id']])
2243 def _inherits_join_add(self, parent_model_name, query):
2245 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2247 :param parent_model_name: name of the parent model for which the clauses should be added
2248 :param query: query object on which the JOIN should be added
2250 inherits_field = self._inherits[parent_model_name]
2251 parent_model = self.pool.get(parent_model_name)
2252 parent_table_name = parent_model._table
2253 quoted_parent_table_name = '"%s"' % parent_table_name
2254 if quoted_parent_table_name not in query.tables:
2255 query.tables.append(quoted_parent_table_name)
2256 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2258 def _inherits_join_calc(self, field, query):
2260 Adds missing table select and join clause(s) to ``query`` for reaching
2261 the field coming from an '_inherits' parent table (no duplicates).
2263 :param field: name of inherited field to reach
2264 :param query: query object on which the JOIN should be added
2265 :return: qualified name of field, to be used in SELECT clause
2267 current_table = self
2268 while field in current_table._inherit_fields and not field in current_table._columns:
2269 parent_model_name = current_table._inherit_fields[field][0]
2270 parent_table = self.pool.get(parent_model_name)
2271 self._inherits_join_add(parent_model_name, query)
2272 current_table = parent_table
2273 return '"%s".%s' % (current_table._table, field)
2275 def _parent_store_compute(self, cr):
2276 if not self._parent_store:
2278 logger = netsvc.Logger()
2279 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2280 def browse_rec(root, pos=0):
2282 where = self._parent_name+'='+str(root)
2284 where = self._parent_name+' IS NULL'
2285 if self._parent_order:
2286 where += ' order by '+self._parent_order
2287 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2289 for id in cr.fetchall():
2290 pos2 = browse_rec(id[0], pos2)
2291 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2293 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2294 if self._parent_order:
2295 query += ' order by ' + self._parent_order
2298 for (root,) in cr.fetchall():
2299 pos = browse_rec(root, pos)
2302 def _update_store(self, cr, f, k):
2303 logger = netsvc.Logger()
2304 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2305 ss = self._columns[k]._symbol_set
2306 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2307 cr.execute('select id from '+self._table)
2308 ids_lst = map(lambda x: x[0], cr.fetchall())
2311 ids_lst = ids_lst[40:]
2312 res = f.get(cr, self, iids, k, 1, {})
2313 for key, val in res.items():
2316 # if val is a many2one, just write the ID
2317 if type(val) == tuple:
2319 if (val<>False) or (type(val)<>bool):
2320 cr.execute(update_query, (ss[1](val), key))
2322 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2323 """Raise except_orm if value is not among the valid values for the selection field"""
2324 if self._columns[field]._type == 'reference':
2325 val_model, val_id_str = value.split(',', 1)
2328 val_id = long(val_id_str)
2332 raise except_orm(_('ValidateError'),
2333 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2337 if isinstance(self._columns[field].selection, (tuple, list)):
2338 if val in dict(self._columns[field].selection):
2340 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2342 raise except_orm(_('ValidateError'),
2343 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2345 def _check_removed_columns(self, cr, log=False):
2346 # iterate on the database columns to drop the NOT NULL constraints
2347 # of fields which were required but have been removed (or will be added by another module)
2348 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2349 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2350 cr.execute("SELECT a.attname, a.attnotnull"
2351 " FROM pg_class c, pg_attribute a"
2352 " WHERE c.relname=%s"
2353 " AND c.oid=a.attrelid"
2354 " AND a.attisdropped=%s"
2355 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2356 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2358 for column in cr.dictfetchall():
2360 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2361 column['attname'], self._table, self._name)
2362 if column['attnotnull']:
2363 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2364 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2365 self._table, column['attname'])
2367 def _auto_init(self, cr, context=None):
2370 store_compute = False
2373 self._field_create(cr, context=context)
2374 if getattr(self, '_auto', True):
2375 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2377 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2378 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2380 self.__schema.debug("Table '%s': created", self._table)
2383 if self._parent_store:
2384 cr.execute("""SELECT c.relname
2385 FROM pg_class c, pg_attribute a
2386 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2387 """, (self._table, 'parent_left'))
2389 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2390 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2391 if 'parent_left' not in self._columns:
2392 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2394 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2395 self._table, 'parent_left', 'INTEGER')
2396 elif not self._columns['parent_left'].select:
2397 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2399 if 'parent_right' not in self._columns:
2400 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2402 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2403 self._table, 'parent_right', 'INTEGER')
2404 elif not self._columns['parent_right'].select:
2405 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2407 if self._columns[self._parent_name].ondelete != 'cascade':
2408 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2409 self._parent_name, self._name)
2412 store_compute = True
2414 if self._log_access:
2416 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2417 'create_date': 'TIMESTAMP',
2418 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2419 'write_date': 'TIMESTAMP'
2424 FROM pg_class c, pg_attribute a
2425 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2426 """, (self._table, k))
2428 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2430 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2431 self._table, k, logs[k])
2433 self._check_removed_columns(cr, log=False)
2435 # iterate on the "object columns"
2436 todo_update_store = []
2437 update_custom_fields = context.get('update_custom_fields', False)
2439 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 " \
2440 "FROM pg_class c,pg_attribute a,pg_type t " \
2441 "WHERE c.relname=%s " \
2442 "AND c.oid=a.attrelid " \
2443 "AND a.atttypid=t.oid", (self._table,))
2444 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2447 for k in self._columns:
2448 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2450 #Not Updating Custom fields
2451 if k.startswith('x_') and not update_custom_fields:
2454 f = self._columns[k]
2456 if isinstance(f, fields.one2many):
2457 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2459 if self.pool.get(f._obj):
2460 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2461 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2462 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2465 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))
2466 res = cr.fetchone()[0]
2468 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2469 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2470 self._obj, f._fields_id, f._table)
2471 elif isinstance(f, fields.many2many):
2472 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2473 if not cr.dictfetchall():
2474 if not self.pool.get(f._obj):
2475 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2476 ref = self.pool.get(f._obj)._table
2477 # ref = f._obj.replace('.', '_')
2478 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))
2479 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2480 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2481 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2483 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2484 f._rel, self._table, ref)
2486 res = col_data.get(k, [])
2487 res = res and [res] or []
2488 if not res and hasattr(f, 'oldname'):
2489 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 " \
2490 "FROM pg_class c,pg_attribute a,pg_type t " \
2491 "WHERE c.relname=%s " \
2492 "AND a.attname=%s " \
2493 "AND c.oid=a.attrelid " \
2494 "AND a.atttypid=t.oid", (self._table, f.oldname))
2495 res_old = cr.dictfetchall()
2496 if res_old and len(res_old) == 1:
2497 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2499 res[0]['attname'] = k
2500 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2501 self._table, f.oldname, k)
2505 f_pg_type = f_pg_def['typname']
2506 f_pg_size = f_pg_def['size']
2507 f_pg_notnull = f_pg_def['attnotnull']
2508 if isinstance(f, fields.function) and not f.store and\
2509 not getattr(f, 'nodrop', False):
2510 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2511 k, f.string, self._table)
2512 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2514 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2518 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2523 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2524 ('varchar', 'text', 'TEXT', ''),
2525 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2526 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2527 ('timestamp', 'date', 'date', '::date'),
2528 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2529 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2531 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2532 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2533 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2534 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2535 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2537 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2538 self._table, k, f_pg_size, f.size)
2540 if (f_pg_type==c[0]) and (f._type==c[1]):
2541 if f_pg_type != f_obj_type:
2543 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2544 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2545 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2546 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2548 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2549 self._table, k, c[0], c[1])
2552 if f_pg_type != f_obj_type:
2556 newname = k + '_moved' + str(i)
2557 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2558 "WHERE c.relname=%s " \
2559 "AND a.attname=%s " \
2560 "AND c.oid=a.attrelid ", (self._table, newname))
2561 if not cr.fetchone()[0]:
2565 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2566 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2567 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2568 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2569 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2570 self._table, k, f_pg_type, f._type, newname)
2572 # if the field is required and hasn't got a NOT NULL constraint
2573 if f.required and f_pg_notnull == 0:
2574 # set the field to the default value if any
2575 if k in self._defaults:
2576 if callable(self._defaults[k]):
2577 default = self._defaults[k](self, cr, 1, context)
2579 default = self._defaults[k]
2581 if (default is not None):
2582 ss = self._columns[k]._symbol_set
2583 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2584 cr.execute(query, (ss[1](default),))
2585 # add the NOT NULL constraint
2588 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2590 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2593 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2594 "If you want to have it, you should update the records and execute manually:\n"\
2595 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2596 self.__schema.warn(msg, self._table, k, self._table, k)
2598 elif not f.required and f_pg_notnull == 1:
2599 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2601 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2604 indexname = '%s_%s_index' % (self._table, k)
2605 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2606 res2 = cr.dictfetchall()
2607 if not res2 and f.select:
2608 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2610 if f._type == 'text':
2611 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2612 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2613 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2614 " because there is a length limit for indexable btree values!\n"\
2615 "Use a search view instead if you simply want to make the field searchable."
2616 self.__schema.warn(msg, self._table, k, f._type)
2617 if res2 and not f.select:
2618 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2620 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2621 self.__schema.debug(msg, self._table, k, f._type)
2623 if isinstance(f, fields.many2one):
2624 ref = self.pool.get(f._obj)._table
2625 if ref != 'ir_actions':
2626 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2627 'pg_attribute as att1, pg_attribute as att2 '
2628 'WHERE con.conrelid = cl1.oid '
2629 'AND cl1.relname = %s '
2630 'AND con.confrelid = cl2.oid '
2631 'AND cl2.relname = %s '
2632 'AND array_lower(con.conkey, 1) = 1 '
2633 'AND con.conkey[1] = att1.attnum '
2634 'AND att1.attrelid = cl1.oid '
2635 'AND att1.attname = %s '
2636 'AND array_lower(con.confkey, 1) = 1 '
2637 'AND con.confkey[1] = att2.attnum '
2638 'AND att2.attrelid = cl2.oid '
2639 'AND att2.attname = %s '
2640 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2641 res2 = cr.dictfetchall()
2643 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2644 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2645 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2647 self.__schema.debug("Table '%s': column '%s': XXX",
2650 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2652 if not isinstance(f, fields.function) or f.store:
2653 # add the missing field
2654 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2655 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2656 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2657 self._table, k, get_pg_type(f)[1])
2660 if not create and k in self._defaults:
2661 if callable(self._defaults[k]):
2662 default = self._defaults[k](self, cr, 1, context)
2664 default = self._defaults[k]
2666 ss = self._columns[k]._symbol_set
2667 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2668 cr.execute(query, (ss[1](default),))
2670 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2672 if isinstance(f, fields.function):
2674 if f.store is not True:
2675 order = f.store[f.store.keys()[0]][2]
2676 todo_update_store.append((order, f, k))
2678 # and add constraints if needed
2679 if isinstance(f, fields.many2one):
2680 if not self.pool.get(f._obj):
2681 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2682 ref = self.pool.get(f._obj)._table
2683 # ref = f._obj.replace('.', '_')
2684 # ir_actions is inherited so foreign key doesn't work on it
2685 if ref != 'ir_actions':
2686 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2687 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2688 self._table, k, ref, f.ondelete)
2690 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2694 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2695 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2698 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2699 "Try to re-run: openerp-server.py --update=module\n"\
2700 "If it doesn't work, update records and execute manually:\n"\
2701 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2702 self.__logger.warn(msg, k, self._table, self._table, k)
2704 for order, f, k in todo_update_store:
2705 todo_end.append((order, self._update_store, (f, k)))
2708 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2709 create = not bool(cr.fetchone())
2711 cr.commit() # start a new transaction
2713 for (key, con, _) in self._sql_constraints:
2714 conname = '%s_%s' % (self._table, key)
2716 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2717 existing_constraints = cr.dictfetchall()
2722 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2723 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2724 self._table, conname, con),
2725 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2730 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2731 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2732 '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" % (
2738 if not existing_constraints:
2739 # constraint does not exists:
2740 sql_actions['add']['execute'] = True
2741 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2742 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2743 # constraint exists but its definition has changed:
2744 sql_actions['drop']['execute'] = True
2745 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2746 sql_actions['add']['execute'] = True
2747 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2749 # we need to add the constraint:
2750 sql_actions = [item for item in sql_actions.values()]
2751 sql_actions.sort(key=lambda x: x['order'])
2752 for sql_action in [action for action in sql_actions if action['execute']]:
2754 cr.execute(sql_action['query'])
2756 self.__schema.debug(sql_action['msg_ok'])
2758 self.__schema.warn(sql_action['msg_err'])
2762 if hasattr(self, "_sql"):
2763 for line in self._sql.split(';'):
2764 line2 = line.replace('\n', '').strip()
2769 self._parent_store_compute(cr)
2773 def __init__(self, cr):
2774 super(orm, self).__init__(cr)
2776 if not hasattr(self, '_log_access'):
2777 # if not access is not specify, it is the same value as _auto
2778 self._log_access = getattr(self, "_auto", True)
2780 self._columns = self._columns.copy()
2781 for store_field in self._columns:
2782 f = self._columns[store_field]
2783 if hasattr(f, 'digits_change'):
2785 if not isinstance(f, fields.function):
2789 if self._columns[store_field].store is True:
2790 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2792 sm = self._columns[store_field].store
2793 for object, aa in sm.items():
2795 (fnct, fields2, order, length) = aa
2797 (fnct, fields2, order) = aa
2800 raise except_orm('Error',
2801 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2802 self.pool._store_function.setdefault(object, [])
2804 for x, y, z, e, f, l in self.pool._store_function[object]:
2805 if (x==self._name) and (y==store_field) and (e==fields2):
2809 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2810 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2812 for (key, _, msg) in self._sql_constraints:
2813 self.pool._sql_error[self._table+'_'+key] = msg
2815 # Load manual fields
2817 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2819 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2820 for field in cr.dictfetchall():
2821 if field['name'] in self._columns:
2824 'string': field['field_description'],
2825 'required': bool(field['required']),
2826 'readonly': bool(field['readonly']),
2827 'domain': eval(field['domain']) if field['domain'] else None,
2828 'size': field['size'],
2829 'ondelete': field['on_delete'],
2830 'translate': (field['translate']),
2832 #'select': int(field['select_level'])
2835 if field['ttype'] == 'selection':
2836 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2837 elif field['ttype'] == 'reference':
2838 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2839 elif field['ttype'] == 'many2one':
2840 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2841 elif field['ttype'] == 'one2many':
2842 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2843 elif field['ttype'] == 'many2many':
2844 _rel1 = field['relation'].replace('.', '_')
2845 _rel2 = field['model'].replace('.', '_')
2846 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2847 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2849 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2850 self._inherits_check()
2851 self._inherits_reload()
2852 if not self._sequence:
2853 self._sequence = self._table + '_id_seq'
2854 for k in self._defaults:
2855 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,)
2856 for f in self._columns:
2857 self._columns[f].restart()
2860 # Update objects that uses this one to update their _inherits fields
2863 def _inherits_reload_src(self):
2864 for obj in self.pool.obj_pool.values():
2865 if self._name in obj._inherits:
2866 obj._inherits_reload()
2868 def _inherits_reload(self):
2870 for table in self._inherits:
2871 res.update(self.pool.get(table)._inherit_fields)
2872 for col in self.pool.get(table)._columns.keys():
2873 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2874 for col in self.pool.get(table)._inherit_fields.keys():
2875 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2876 self._inherit_fields = res
2877 self._inherits_reload_src()
2879 def _inherits_check(self):
2880 for table, field_name in self._inherits.items():
2881 if field_name not in self._columns:
2882 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2883 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2884 required=True, ondelete="cascade")
2885 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2886 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))
2887 self._columns[field_name].required = True
2888 self._columns[field_name].ondelete = "cascade"
2890 #def __getattr__(self, name):
2892 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2893 # (though inherits doesn't use Python inheritance).
2894 # Handles translating between local ids and remote ids.
2895 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2896 # when you have inherits.
2898 # for model, field in self._inherits.iteritems():
2899 # proxy = self.pool.get(model)
2900 # if hasattr(proxy, name):
2901 # attribute = getattr(proxy, name)
2902 # if not hasattr(attribute, '__call__'):
2906 # return super(orm, self).__getattr__(name)
2908 # def _proxy(cr, uid, ids, *args, **kwargs):
2909 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2910 # lst = [obj[field].id for obj in objects if obj[field]]
2911 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2916 def fields_get(self, cr, user, fields=None, context=None):
2918 Get the description of list of fields
2920 :param cr: database cursor
2921 :param user: current user id
2922 :param fields: list of fields
2923 :param context: context arguments, like lang, time zone
2924 :return: dictionary of field dictionaries, each one describing a field of the business object
2925 :raise AccessError: * if user has no create/write rights on the requested object
2928 ira = self.pool.get('ir.model.access')
2929 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2930 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2931 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2933 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2936 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2938 fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
2939 if isinstance(ids, (int, long)):
2943 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
2944 result = self._read_flat(cr, user, select, fields, context, load)
2947 for key, v in r.items():
2951 if isinstance(ids, (int, long, dict)):
2952 return result and result[0] or False
2955 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2960 if fields_to_read == None:
2961 fields_to_read = self._columns.keys()
2963 # Construct a clause for the security rules.
2964 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2965 # or will at least contain self._table.
2966 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2968 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2969 fields_pre = [f for f in fields_to_read if
2970 f == self.CONCURRENCY_CHECK_FIELD
2971 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2972 ] + self._inherits.values()
2976 def convert_field(f):
2977 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2978 if f in ('create_date', 'write_date'):
2979 return "date_trunc('second', %s) as %s" % (f_qual, f)
2980 if f == self.CONCURRENCY_CHECK_FIELD:
2981 if self._log_access:
2982 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2983 return "now()::timestamp AS %s" % (f,)
2984 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2985 return 'length(%s) as "%s"' % (f_qual, f)
2988 fields_pre2 = map(convert_field, fields_pre)
2989 order_by = self._parent_order or self._order
2990 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2991 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2993 query += " AND " + (' OR '.join(rule_clause))
2994 query += " ORDER BY " + order_by
2995 for sub_ids in cr.split_for_in_conditions(ids):
2997 cr.execute(query, [tuple(sub_ids)] + rule_params)
2998 if cr.rowcount != len(sub_ids):
2999 raise except_orm(_('AccessError'),
3000 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3001 % (self._description,))
3003 cr.execute(query, (tuple(sub_ids),))
3004 res.extend(cr.dictfetchall())
3006 res = map(lambda x: {'id': x}, ids)
3008 for f in fields_pre:
3009 if f == self.CONCURRENCY_CHECK_FIELD:
3011 if self._columns[f].translate:
3012 ids = [x['id'] for x in res]
3013 #TODO: optimize out of this loop
3014 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3016 r[f] = res_trans.get(r['id'], False) or r[f]
3018 for table in self._inherits:
3019 col = self._inherits[table]
3020 cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
3023 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3031 if not record[col]: # if the record is deleted from _inherits table?
3033 record.update(res3[record[col]])
3034 if col not in fields_to_read:
3037 # all fields which need to be post-processed by a simple function (symbol_get)
3038 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3041 for f in fields_post:
3042 r[f] = self._columns[f]._symbol_get(r[f])
3043 ids = [x['id'] for x in res]
3045 # all non inherited fields for which the attribute whose name is in load is False
3046 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3048 # Compute POST fields
3050 for f in fields_post:
3051 todo.setdefault(self._columns[f]._multi, [])
3052 todo[self._columns[f]._multi].append(f)
3053 for key, val in todo.items():
3055 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3058 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3059 multi_fields = res2.get(record['id'],{})
3061 record[pos] = multi_fields.get(pos,[])
3064 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3067 record[f] = res2[record['id']]
3072 for field in vals.copy():
3074 if field in self._columns:
3075 fobj = self._columns[field]
3082 for group in groups:
3083 module = group.split(".")[0]
3084 grp = group.split(".")[1]
3085 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", \
3086 (grp, module, 'res.groups', user))
3087 readonly = cr.fetchall()
3088 if readonly[0][0] >= 1:
3091 elif readonly[0][0] == 0:
3097 if type(vals[field]) == type([]):
3099 elif type(vals[field]) == type(0.0):
3101 elif type(vals[field]) == type(''):
3102 vals[field] = '=No Permission='
3107 def perm_read(self, cr, user, ids, context=None, details=True):
3109 Returns some metadata about the given records.
3111 :param details: if True, \*_uid fields are replaced with the name of the user
3112 :return: list of ownership dictionaries for each requested record
3113 :rtype: list of dictionaries with the following keys:
3116 * create_uid: user who created the record
3117 * create_date: date when the record was created
3118 * write_uid: last user who changed the record
3119 * write_date: date of the last change to the record
3120 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3127 uniq = isinstance(ids, (int, long))
3131 if self._log_access:
3132 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3133 quoted_table = '"%s"' % self._table
3134 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3135 query = '''SELECT %s, __imd.module, __imd.name
3136 FROM %s LEFT JOIN ir_model_data __imd
3137 ON (__imd.model = %%s and __imd.res_id = %s.id)
3138 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3139 cr.execute(query, (self._name, tuple(ids)))
3140 res = cr.dictfetchall()
3143 r[key] = r[key] or False
3144 if details and key in ('write_uid', 'create_uid') and r[key]:
3146 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3148 pass # Leave the numeric uid there
3149 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3150 del r['name'], r['module']
3155 def _check_concurrency(self, cr, ids, context):
3158 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3160 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3161 for sub_ids in cr.split_for_in_conditions(ids):
3164 id_ref = "%s,%s" % (self._name, id)
3165 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3167 ids_to_check.extend([id, update_date])
3168 if not ids_to_check:
3170 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3173 # mention the first one only to keep the error message readable
3174 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3176 def check_access_rule(self, cr, uid, ids, operation, context=None):
3177 """Verifies that the operation given by ``operation`` is allowed for the user
3178 according to ir.rules.
3180 :param operation: one of ``write``, ``unlink``
3181 :raise except_orm: * if current ir.rules do not permit this operation.
3182 :return: None if the operation is allowed
3184 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3186 where_clause = ' and ' + ' and '.join(where_clause)
3187 for sub_ids in cr.split_for_in_conditions(ids):
3188 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3189 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3190 [sub_ids] + where_params)
3191 if cr.rowcount != len(sub_ids):
3192 raise except_orm(_('AccessError'),
3193 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3194 % (operation, self._description))
3196 def unlink(self, cr, uid, ids, context=None):
3198 Delete records with given ids
3200 :param cr: database cursor
3201 :param uid: current user id
3202 :param ids: id or list of ids
3203 :param context: (optional) context arguments, like lang, time zone
3205 :raise AccessError: * if user has no unlink rights on the requested object
3206 * if user tries to bypass access rules for unlink on the requested object
3207 :raise UserError: if the record is default property for other records
3212 if isinstance(ids, (int, long)):
3215 result_store = self._store_get_values(cr, uid, ids, None, context)
3217 self._check_concurrency(cr, ids, context)
3219 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3221 properties = self.pool.get('ir.property')
3222 domain = [('res_id', '=', False),
3223 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3225 if properties.search(cr, uid, domain, context=context):
3226 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3228 wf_service = netsvc.LocalService("workflow")
3230 wf_service.trg_delete(uid, self._name, oid, cr)
3233 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3234 pool_model_data = self.pool.get('ir.model.data')
3235 pool_ir_values = self.pool.get('ir.values')
3236 for sub_ids in cr.split_for_in_conditions(ids):
3237 cr.execute('delete from ' + self._table + ' ' \
3238 'where id IN %s', (sub_ids,))
3240 # Removing the ir_model_data reference if the record being deleted is a record created by xml/csv file,
3241 # as these are not connected with real database foreign keys, and would be dangling references.
3242 # Step 1. Calling unlink of ir_model_data only for the affected IDS.
3243 referenced_ids = pool_model_data.search(cr, uid, [('res_id','in',list(sub_ids)),('model','=',self._name)], context=context)
3244 # Step 2. Marching towards the real deletion of referenced records
3245 pool_model_data.unlink(cr, uid, referenced_ids, context=context)
3247 # For the same reason, removing the record relevant to ir_values
3248 ir_value_ids = pool_ir_values.search(cr, uid,
3249 ['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
3252 pool_ir_values.unlink(cr, uid, ir_value_ids, context=context)
3254 for order, object, store_ids, fields in result_store:
3255 if object != self._name:
3256 obj = self.pool.get(object)
3257 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3258 rids = map(lambda x: x[0], cr.fetchall())
3260 obj._store_set_values(cr, uid, rids, fields, context)
3267 def write(self, cr, user, ids, vals, context=None):
3269 Update records with given ids with the given field values
3271 :param cr: database cursor
3272 :param user: current user id
3274 :param ids: object id or list of object ids to update according to **vals**
3275 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3276 :type vals: dictionary
3277 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3278 :type context: dictionary
3280 :raise AccessError: * if user has no write rights on the requested object
3281 * if user tries to bypass access rules for write on the requested object
3282 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3283 :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)
3285 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3287 + For a many2many field, a list of tuples is expected.
3288 Here is the list of tuple that are accepted, with the corresponding semantics ::
3290 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3291 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3292 (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)
3293 (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)
3294 (4, ID) link to existing record with id = ID (adds a relationship)
3295 (5) unlink all (like using (3,ID) for all linked records)
3296 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3299 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3301 + For a one2many field, a lits of tuples is expected.
3302 Here is the list of tuple that are accepted, with the corresponding semantics ::
3304 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3305 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3306 (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)
3309 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3311 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3312 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3316 for field in vals.copy():
3318 if field in self._columns:
3319 fobj = self._columns[field]
3320 elif field in self._inherit_fields:
3321 fobj = self._inherit_fields[field][2]
3328 for group in groups:
3329 module = group.split(".")[0]
3330 grp = group.split(".")[1]
3331 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", \
3332 (grp, module, 'res.groups', user))
3333 readonly = cr.fetchall()
3334 if readonly[0][0] >= 1:
3337 elif readonly[0][0] == 0:
3349 if isinstance(ids, (int, long)):
3352 self._check_concurrency(cr, ids, context)
3353 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3355 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3357 # No direct update of parent_left/right
3358 vals.pop('parent_left', None)
3359 vals.pop('parent_right', None)
3361 parents_changed = []
3362 if self._parent_store and (self._parent_name in vals):
3363 # The parent_left/right computation may take up to
3364 # 5 seconds. No need to recompute the values if the
3365 # parent is the same. Get the current value of the parent
3366 parent_val = vals[self._parent_name]
3368 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3369 (self._table, self._parent_name, self._parent_name)
3370 cr.execute(query, (tuple(ids), parent_val))
3372 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3373 (self._table, self._parent_name)
3374 cr.execute(query, (tuple(ids),))
3375 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3382 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3384 if field in self._columns:
3385 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3386 if (not totranslate) or not self._columns[field].translate:
3387 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3388 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3389 direct.append(field)
3391 upd_todo.append(field)
3393 updend.append(field)
3394 if field in self._columns \
3395 and hasattr(self._columns[field], 'selection') \
3397 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3399 if self._log_access:
3400 upd0.append('write_uid=%s')
3401 upd0.append('write_date=now()')
3405 self.check_access_rule(cr, user, ids, 'write', context=context)
3406 for sub_ids in cr.split_for_in_conditions(ids):
3407 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3408 'where id IN %s', upd1 + [sub_ids])
3409 if cr.rowcount != len(sub_ids):
3410 raise except_orm(_('AccessError'),
3411 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3416 if self._columns[f].translate:
3417 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3420 # Inserting value to DB
3421 self.write(cr, user, ids, {f: vals[f]})
3422 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3425 # call the 'set' method of fields which are not classic_write
3426 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3428 # default element in context must be removed when call a one2many or many2many
3429 rel_context = context.copy()
3430 for c in context.items():
3431 if c[0].startswith('default_'):
3432 del rel_context[c[0]]
3434 for field in upd_todo:
3436 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3438 for table in self._inherits:
3439 col = self._inherits[table]
3441 for sub_ids in cr.split_for_in_conditions(ids):
3442 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3443 'where id IN %s', (sub_ids,))
3444 nids.extend([x[0] for x in cr.fetchall()])
3448 if self._inherit_fields[val][0] == table:
3451 self.pool.get(table).write(cr, user, nids, v, context)
3453 self._validate(cr, user, ids, context)
3455 # TODO: use _order to set dest at the right position and not first node of parent
3456 # We can't defer parent_store computation because the stored function
3457 # fields that are computer may refer (directly or indirectly) to
3458 # parent_left/right (via a child_of domain)
3461 self.pool._init_parent[self._name] = True
3463 order = self._parent_order or self._order
3464 parent_val = vals[self._parent_name]
3466 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3468 clause, params = '%s IS NULL' % (self._parent_name,), ()
3470 for id in parents_changed:
3471 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3472 pleft, pright = cr.fetchone()
3473 distance = pright - pleft + 1
3475 # Positions of current siblings, to locate proper insertion point;
3476 # this can _not_ be fetched outside the loop, as it needs to be refreshed
3477 # after each update, in case several nodes are sequentially inserted one
3478 # next to the other (i.e computed incrementally)
3479 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3480 parents = cr.fetchall()
3482 # Find Position of the element
3484 for (parent_pright, parent_id) in parents:
3487 position = parent_pright + 1
3489 # It's the first node of the parent
3494 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3495 position = cr.fetchone()[0] + 1
3497 if pleft < position <= pright:
3498 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3500 if pleft < position:
3501 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3502 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3503 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))
3505 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3506 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3507 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))
3509 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3513 for order, object, ids_to_update, fields_to_recompute in result:
3514 key = (object, tuple(fields_to_recompute))
3515 done.setdefault(key, {})
3516 # avoid to do several times the same computation
3518 for id in ids_to_update:
3519 if id not in done[key]:
3520 done[key][id] = True
3522 self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
3524 wf_service = netsvc.LocalService("workflow")
3526 wf_service.trg_write(user, self._name, id, cr)
3530 # TODO: Should set perm to user.xxx
3532 def create(self, cr, user, vals, context=None):
3534 Create new record with specified value
3536 :param cr: database cursor
3537 :param user: current user id
3539 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3540 :type vals: dictionary
3541 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3542 :type context: dictionary
3543 :return: id of new record created
3544 :raise AccessError: * if user has no create rights on the requested object
3545 * if user tries to bypass access rules for create on the requested object
3546 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3547 :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)
3549 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3550 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3556 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3558 vals = self._add_missing_default_values(cr, user, vals, context)
3561 for v in self._inherits:
3562 if self._inherits[v] not in vals:
3565 tocreate[v] = {'id': vals[self._inherits[v]]}
3566 (upd0, upd1, upd2) = ('', '', [])
3568 for v in vals.keys():
3569 if v in self._inherit_fields:
3570 (table, col, col_detail) = self._inherit_fields[v]
3571 tocreate[table][v] = vals[v]
3574 if (v not in self._inherit_fields) and (v not in self._columns):
3577 # Try-except added to filter the creation of those records whose filds are readonly.
3578 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3580 cr.execute("SELECT nextval('"+self._sequence+"')")
3582 raise except_orm(_('UserError'),
3583 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3585 id_new = cr.fetchone()[0]
3586 for table in tocreate:
3587 if self._inherits[table] in vals:
3588 del vals[self._inherits[table]]
3590 record_id = tocreate[table].pop('id', None)
3592 if record_id is None or not record_id:
3593 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3595 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3597 upd0 += ',' + self._inherits[table]
3599 upd2.append(record_id)
3601 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3602 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3604 for bool_field in bool_fields:
3605 if bool_field not in vals:
3606 vals[bool_field] = False
3608 for field in vals.copy():
3610 if field in self._columns:
3611 fobj = self._columns[field]
3613 fobj = self._inherit_fields[field][2]
3619 for group in groups:
3620 module = group.split(".")[0]
3621 grp = group.split(".")[1]
3622 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" % \
3623 (grp, module, 'res.groups', user))
3624 readonly = cr.fetchall()
3625 if readonly[0][0] >= 1:
3628 elif readonly[0][0] == 0:
3636 if self._columns[field]._classic_write:
3637 upd0 = upd0 + ',"' + field + '"'
3638 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3639 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3641 if not isinstance(self._columns[field], fields.related):
3642 upd_todo.append(field)
3643 if field in self._columns \
3644 and hasattr(self._columns[field], 'selection') \
3646 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3647 if self._log_access:
3648 upd0 += ',create_uid,create_date'
3651 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3652 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3653 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3655 if self._parent_store and not context.get('defer_parent_store_computation'):
3657 self.pool._init_parent[self._name] = True
3659 parent = vals.get(self._parent_name, False)
3661 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3663 result_p = cr.fetchall()
3664 for (pleft,) in result_p:
3669 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3670 pleft_old = cr.fetchone()[0]
3673 cr.execute('select max(parent_right) from '+self._table)
3674 pleft = cr.fetchone()[0] or 0
3675 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3676 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3677 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3679 # default element in context must be remove when call a one2many or many2many
3680 rel_context = context.copy()
3681 for c in context.items():
3682 if c[0].startswith('default_'):
3683 del rel_context[c[0]]
3686 for field in upd_todo:
3687 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3688 self._validate(cr, user, [id_new], context)
3690 if not context.get('no_store_function', False):
3691 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3694 for order, object, ids, fields2 in result:
3695 if not (object, ids, fields2) in done:
3696 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3697 done.append((object, ids, fields2))
3699 if self._log_create and not (context and context.get('no_store_function', False)):
3700 message = self._description + \
3702 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3703 "' " + _("created.")
3704 self.log(cr, user, id_new, message, True, context=context)
3705 wf_service = netsvc.LocalService("workflow")
3706 wf_service.trg_create(user, self._name, id_new, cr)
3709 def _store_get_values(self, cr, uid, ids, fields, context):
3710 """Returns an ordered list of fields.functions to call due to
3711 an update operation on ``fields`` of records with ``ids``,
3712 obtained by calling the 'store' functions of these fields,
3713 as setup by their 'store' attribute.
3715 :return: [(priority, model_name, [record_ids,], [function_fields,])]
3717 # FIXME: rewrite, cleanup, use real variable names
3718 # e.g.: http://pastie.org/1222060
3720 fncts = self.pool._store_function.get(self._name, [])
3721 for fnct in range(len(fncts)):
3726 for f in (fields or []):
3727 if f in fncts[fnct][3]:
3733 result.setdefault(fncts[fnct][0], {})
3735 # uid == 1 for accessing objects having rules defined on store fields
3736 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3737 for id in filter(None, ids2):
3738 result[fncts[fnct][0]].setdefault(id, [])
3739 result[fncts[fnct][0]][id].append(fnct)
3741 for object in result:
3743 for id, fnct in result[object].items():
3744 k2.setdefault(tuple(fnct), [])
3745 k2[tuple(fnct)].append(id)
3746 for fnct, id in k2.items():
3747 dict.setdefault(fncts[fnct[0]][4], [])
3748 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3756 def _store_set_values(self, cr, uid, ids, fields, context):
3757 """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
3758 respecting ``multi`` attributes), and stores the resulting values in the database directly."""
3763 if self._log_access:
3764 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3768 field_dict.setdefault(r[0], [])
3769 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3770 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3771 for i in self.pool._store_function.get(self._name, []):
3773 up_write_date = write_date + datetime.timedelta(hours=i[5])
3774 if datetime.datetime.now() < up_write_date:
3776 field_dict[r[0]].append(i[1])
3782 if self._columns[f]._multi not in keys:
3783 keys.append(self._columns[f]._multi)
3784 todo.setdefault(self._columns[f]._multi, [])
3785 todo[self._columns[f]._multi].append(f)
3789 # uid == 1 for accessing objects having rules defined on store fields
3790 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3791 for id, value in result.items():
3793 for f in value.keys():
3794 if f in field_dict[id]:
3801 if self._columns[v]._type in ('many2one', 'one2one'):
3803 value[v] = value[v][0]
3806 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3807 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3810 cr.execute('update "' + self._table + '" set ' + \
3811 ','.join(upd0) + ' where id = %s', upd1)
3815 # uid == 1 for accessing objects having rules defined on store fields
3816 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3817 for r in result.keys():
3819 if r in field_dict.keys():
3820 if f in field_dict[r]:
3822 for id, value in result.items():
3823 if self._columns[f]._type in ('many2one', 'one2one'):
3828 cr.execute('update "' + self._table + '" set ' + \
3829 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3835 def perm_write(self, cr, user, ids, fields, context=None):
3836 raise NotImplementedError(_('This method does not exist anymore'))
3838 # TODO: ameliorer avec NULL
3839 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3840 """Computes the WHERE clause needed to implement an OpenERP domain.
3841 :param domain: the domain to compute
3843 :param active_test: whether the default filtering of records with ``active``
3844 field set to ``False`` should be applied.
3845 :return: the query expressing the given domain as provided in domain
3846 :rtype: osv.query.Query
3851 # if the object has a field named 'active', filter out all inactive
3852 # records unless they were explicitely asked for
3853 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3855 active_in_args = False
3857 if a[0] == 'active':
3858 active_in_args = True
3859 if not active_in_args:
3860 domain.insert(0, ('active', '=', 1))
3862 domain = [('active', '=', 1)]
3866 e = expression.expression(domain)
3867 e.parse(cr, user, self, context)
3868 tables = e.get_tables()
3869 where_clause, where_params = e.to_sql()
3870 where_clause = where_clause and [where_clause] or []
3872 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3874 return Query(tables, where_clause, where_params)
3876 def _check_qorder(self, word):
3877 if not regex_order.match(word):
3878 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)'))
3881 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3882 """Add what's missing in ``query`` to implement all appropriate ir.rules
3883 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3885 :param query: the current query object
3887 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3889 if parent_model and child_object:
3890 # as inherited rules are being applied, we need to add the missing JOIN
3891 # to reach the parent table (if it was not JOINed yet in the query)
3892 child_object._inherits_join_add(parent_model, query)
3893 query.where_clause += added_clause
3894 query.where_clause_params += added_params
3895 for table in added_tables:
3896 if table not in query.tables:
3897 query.tables.append(table)
3901 # apply main rules on the object
3902 rule_obj = self.pool.get('ir.rule')
3903 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3905 # apply ir.rules from the parents (through _inherits)
3906 for inherited_model in self._inherits:
3907 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3908 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3910 def _generate_m2o_order_by(self, order_field, query):
3912 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3913 either native m2o fields or function/related fields that are stored, including
3914 intermediate JOINs for inheritance if required.
3916 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3918 if order_field not in self._columns and order_field in self._inherit_fields:
3919 # also add missing joins for reaching the table containing the m2o field
3920 qualified_field = self._inherits_join_calc(order_field, query)
3921 order_field_column = self._inherit_fields[order_field][2]
3923 qualified_field = '"%s"."%s"' % (self._table, order_field)
3924 order_field_column = self._columns[order_field]
3926 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3927 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
3928 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
3929 "to be used as ordering fields! Ignoring sorting for %s.%s",
3930 self._name, order_field)
3933 # figure out the applicable order_by for the m2o
3934 dest_model = self.pool.get(order_field_column._obj)
3935 m2o_order = dest_model._order
3936 if not regex_order.match(m2o_order):
3937 # _order is complex, can't use it here, so we default to _rec_name
3938 m2o_order = dest_model._rec_name
3940 # extract the field names, to be able to qualify them and add desc/asc
3942 for order_part in m2o_order.split(","):
3943 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
3944 m2o_order = m2o_order_list
3946 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3947 # as we don't want to exclude results that have NULL values for the m2o
3948 src_table, src_field = qualified_field.replace('"','').split('.', 1)
3949 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3950 qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
3951 return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
3954 def _generate_order_by(self, order_spec, query):
3956 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
3957 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
3959 :raise" except_orm in case order_spec is malformed
3961 order_by_clause = self._order
3963 order_by_elements = []
3964 self._check_qorder(order_spec)
3965 for order_part in order_spec.split(','):
3966 order_split = order_part.strip().split(' ')
3967 order_field = order_split[0].strip()
3968 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
3970 if order_field == 'id':
3971 order_by_clause = '"%s"."%s"' % (self._table, order_field)
3972 elif order_field in self._columns:
3973 order_column = self._columns[order_field]
3974 if order_column._classic_read:
3975 inner_clause = '"%s"."%s"' % (self._table, order_field)
3976 elif order_column._type == 'many2one':
3977 inner_clause = self._generate_m2o_order_by(order_field, query)
3979 continue # ignore non-readable or "non-joinable" fields
3980 elif order_field in self._inherit_fields:
3981 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
3982 order_column = parent_obj._columns[order_field]
3983 if order_column._classic_read:
3984 inner_clause = self._inherits_join_calc(order_field, query)
3985 elif order_column._type == 'many2one':
3986 inner_clause = self._generate_m2o_order_by(order_field, query)
3988 continue # ignore non-readable or "non-joinable" fields
3990 if isinstance(inner_clause, list):
3991 for clause in inner_clause:
3992 order_by_elements.append("%s %s" % (clause, order_direction))
3994 order_by_elements.append("%s %s" % (inner_clause, order_direction))
3995 if order_by_elements:
3996 order_by_clause = ",".join(order_by_elements)
3998 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4000 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4002 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4003 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4004 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4005 This is ok at the security level because this method is private and not callable through XML-RPC.
4007 :param access_rights_uid: optional user ID to use when checking access rights
4008 (not for ir.rules, this is only for ir.model.access)
4012 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4014 query = self._where_calc(cr, user, args, context=context)
4015 self._apply_ir_rules(cr, user, query, 'read', context=context)
4016 order_by = self._generate_order_by(order, query)
4017 from_clause, where_clause, where_clause_params = query.get_sql()
4019 limit_str = limit and ' limit %d' % limit or ''
4020 offset_str = offset and ' offset %d' % offset or ''
4021 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4024 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4027 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4029 return [x[0] for x in res]
4031 # returns the different values ever entered for one field
4032 # this is used, for example, in the client when the user hits enter on
4034 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4037 if field in self._inherit_fields:
4038 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4040 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4042 def copy_data(self, cr, uid, id, default=None, context=None):
4044 Copy given record's data with all its fields values
4046 :param cr: database cursor
4047 :param user: current user id
4048 :param id: id of the record to copy
4049 :param default: field values to override in the original values of the copied record
4050 :type default: dictionary
4051 :param context: context arguments, like lang, time zone
4052 :type context: dictionary
4053 :return: dictionary containing all the field values
4059 # avoid recursion through already copied records in case of circular relationship
4060 seen_map = context.setdefault('__copy_data_seen',{})
4061 if id in seen_map.setdefault(self._name,[]):
4063 seen_map[self._name].append(id)
4067 if 'state' not in default:
4068 if 'state' in self._defaults:
4069 if callable(self._defaults['state']):
4070 default['state'] = self._defaults['state'](self, cr, uid, context)
4072 default['state'] = self._defaults['state']
4074 context_wo_lang = context.copy()
4075 if 'lang' in context:
4076 del context_wo_lang['lang']
4077 data = self.read(cr, uid, [id,], context=context_wo_lang)
4081 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4083 fields = self.fields_get(cr, uid, context=context)
4085 ftype = fields[f]['type']
4087 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4091 data[f] = default[f]
4092 elif 'function' in fields[f]:
4094 elif ftype == 'many2one':
4096 data[f] = data[f] and data[f][0]
4099 elif ftype in ('one2many', 'one2one'):
4101 rel = self.pool.get(fields[f]['relation'])
4103 # duplicate following the order of the ids
4104 # because we'll rely on it later for copying
4105 # translations in copy_translation()!
4107 for rel_id in data[f]:
4108 # the lines are first duplicated using the wrong (old)
4109 # parent but then are reassigned to the correct one thanks
4110 # to the (0, 0, ...)
4111 d = rel.copy_data(cr, uid, rel_id, context=context)
4113 res.append((0, 0, d))
4115 elif ftype == 'many2many':
4116 data[f] = [(6, 0, data[f])]
4120 # make sure we don't break the current parent_store structure and
4121 # force a clean recompute!
4122 for parent_column in ['parent_left', 'parent_right']:
4123 data.pop(parent_column, None)
4125 for v in self._inherits:
4126 del data[self._inherits[v]]
4129 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4133 # avoid recursion through already copied records in case of circular relationship
4134 seen_map = context.setdefault('__copy_translations_seen',{})
4135 if old_id in seen_map.setdefault(self._name,[]):
4137 seen_map[self._name].append(old_id)
4139 trans_obj = self.pool.get('ir.translation')
4140 fields = self.fields_get(cr, uid, context=context)
4142 translation_records = []
4143 for field_name, field_def in fields.items():
4144 # we must recursively copy the translations for o2o and o2m
4145 if field_def['type'] in ('one2one', 'one2many'):
4146 target_obj = self.pool.get(field_def['relation'])
4147 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4148 # here we rely on the order of the ids to match the translations
4149 # as foreseen in copy_data()
4150 old_children = sorted(old_record[field_name])
4151 new_children = sorted(new_record[field_name])
4152 for (old_child, new_child) in zip(old_children, new_children):
4153 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4154 # and for translatable fields we keep them for copy
4155 elif field_def.get('translate'):
4157 if field_name in self._columns:
4158 trans_name = self._name + "," + field_name
4159 elif field_name in self._inherit_fields:
4160 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4162 trans_ids = trans_obj.search(cr, uid, [
4163 ('name', '=', trans_name),
4164 ('res_id', '=', old_id)
4166 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4168 for record in translation_records:
4170 record['res_id'] = new_id
4171 trans_obj.create(cr, uid, record, context=context)
4174 def copy(self, cr, uid, id, default=None, context=None):
4176 Duplicate record with given id updating it with default values
4178 :param cr: database cursor
4179 :param uid: current user id
4180 :param id: id of the record to copy
4181 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4182 :type default: dictionary
4183 :param context: context arguments, like lang, time zone
4184 :type context: dictionary
4190 context = context.copy()
4191 data = self.copy_data(cr, uid, id, default, context)
4192 new_id = self.create(cr, uid, data, context)
4193 self.copy_translations(cr, uid, id, new_id, context)
4196 def exists(self, cr, uid, ids, context=None):
4197 if type(ids) in (int, long):
4199 query = 'SELECT count(1) FROM "%s"' % (self._table)
4200 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4201 return cr.fetchone()[0] == len(ids)
4203 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4204 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4205 self._name, DeprecationWarning, stacklevel=3)
4206 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4207 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4208 return self._check_recursion(cr, uid, ids, context, parent)
4210 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4212 Verifies that there is no loop in a hierarchical structure of records,
4213 by following the parent relationship using the **parent** field until a loop
4214 is detected or until a top-level record is found.
4216 :param cr: database cursor
4217 :param uid: current user id
4218 :param ids: list of ids of records to check
4219 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4220 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4224 parent = self._parent_name
4226 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4229 for i in range(0, len(ids), cr.IN_MAX):
4230 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4231 cr.execute(query, (tuple(sub_ids_parent),))
4232 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4233 ids_parent = ids_parent2
4234 for i in ids_parent:
4239 def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
4240 """Find out the XML ID(s) of any database record.
4242 **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
4244 :return: map of ids to the list of their fully qualified XML IDs
4245 (empty list when there's none).
4247 model_data_obj = self.pool.get('ir.model.data')
4248 data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
4249 data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
4252 # can't use dict.fromkeys() as the list would be shared!
4254 for record in data_results:
4255 result[record['res_id']].append('%(module)s.%(name)s' % record)
4258 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4259 """Find out the XML ID of any database record, if there
4260 is one. This method works as a possible implementation
4261 for a function field, to be able to add it to any
4262 model object easily, referencing it as ``osv.osv.get_xml_id``.
4264 When multiple XML IDs exist for a record, only one
4265 of them is returned (randomly).
4267 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4269 :return: map of ids to their fully qualified XML ID,
4270 defaulting to an empty string when there's none
4271 (to be usable as a function field).
4273 results = self._get_xml_ids(cr, uid, ids)
4274 for k, v in results.items():
4281 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: