1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 # Object relationnal mapping to postgresql module
24 # . Hierarchical structure
25 # . Constraints consistency, validations
26 # . Object meta Data depends on its status
27 # . Optimised processing by complex query (multiple actions at once)
28 # . Default fields value
29 # . Permissions optimisation
30 # . Persistant object: DB postgresql
32 # . Multi-level caching system
33 # . 2 different inheritancies
35 # - classicals (varchar, integer, boolean, ...)
36 # - relations (one2many, many2one, many2many)
53 from lxml import etree
54 from tools.config import config
55 from tools.translate import _
58 from query import Query
60 from tools.safe_eval import safe_eval as eval
62 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
65 POSTGRES_CONFDELTYPES = {
73 # List of etree._Element subclasses that we choose to ignore when parsing view architecture.
74 # We include the *Base ones just in case, currently they seem to be subclasses of the _* ones.
75 SKIPPED_ELEMENT_TYPES = (etree._Comment, etree._ProcessingInstruction, etree.CommentBase, etree.PIBase)
77 def last_day_of_current_month():
78 today = datetime.date.today()
79 last_day = str(calendar.monthrange(today.year, today.month)[1])
80 return time.strftime('%Y-%m-' + last_day)
82 def intersect(la, lb):
83 return filter(lambda x: x in lb, la)
85 class except_orm(Exception):
86 def __init__(self, name, value):
89 self.args = (name, value)
91 class BrowseRecordError(Exception):
94 # Readonly python database object browser
95 class browse_null(object):
100 def __getitem__(self, name):
103 def __getattr__(self, name):
104 return None # XXX: return self ?
112 def __nonzero__(self):
115 def __unicode__(self):
120 # TODO: execute an object method on browse_record_list
122 class browse_record_list(list):
124 def __init__(self, lst, context=None):
127 super(browse_record_list, self).__init__(lst)
128 self.context = context
131 class browse_record(object):
132 logger = netsvc.Logger()
134 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
136 table : the object (inherited from orm)
137 context : dictionary with an optional context
139 if fields_process is None:
143 self._list_class = list_class or browse_record_list
148 self._table_name = self._table._name
149 self.__logger = logging.getLogger(
150 'osv.browse_record.' + self._table_name)
151 self._context = context
152 self._fields_process = fields_process
154 cache.setdefault(table._name, {})
155 self._data = cache[table._name]
157 if not (id and isinstance(id, (int, long,))):
158 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
159 # if not table.exists(cr, uid, id, context):
160 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
162 if id not in self._data:
163 self._data[id] = {'id': id}
167 def __getitem__(self, name):
171 if name not in self._data[self._id]:
172 # build the list of fields we will fetch
174 # fetch the definition of the field which was asked for
175 if name in self._table._columns:
176 col = self._table._columns[name]
177 elif name in self._table._inherit_fields:
178 col = self._table._inherit_fields[name][2]
179 elif hasattr(self._table, str(name)):
180 attr = getattr(self._table, name)
182 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
183 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
187 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
188 "Field '%s' does not exist in object '%s': \n%s" % (
189 name, self, ''.join(traceback.format_exc())))
190 raise KeyError("Field '%s' does not exist in object '%s'" % (
193 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
195 # gen the list of "local" (ie not inherited) fields which are classic or many2one
196 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
197 # gen the list of inherited fields
198 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
199 # complete the field list with the inherited fields which are classic or many2one
200 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
201 # otherwise we fetch only that field
203 fields_to_fetch = [(name, col)]
204 ids = filter(lambda id: name not in self._data[id], self._data.keys())
206 field_names = map(lambda x: x[0], fields_to_fetch)
207 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
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):
243 new_data[field_name] = browse_record(self._cr,
244 self._uid, value, obj, self._cache,
245 context=self._context,
246 list_class=self._list_class,
247 fields_process=self._fields_process)
249 new_data[field_name] = value
251 new_data[field_name] = browse_null()
253 new_data[field_name] = browse_null()
254 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
255 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)
256 elif field_column._type in ('reference'):
257 if result_line[field_name]:
258 if isinstance(result_line[field_name], browse_record):
259 new_data[field_name] = result_line[field_name]
261 ref_obj, ref_id = result_line[field_name].split(',')
262 ref_id = long(ref_id)
264 obj = self._table.pool.get(ref_obj)
265 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)
267 new_data[field_name] = browse_null()
269 new_data[field_name] = browse_null()
271 new_data[field_name] = result_line[field_name]
272 self._data[result_line['id']].update(new_data)
274 if not name in self._data[self._id]:
275 #how did this happen?
276 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
277 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
278 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
279 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
280 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
281 return self._data[self._id][name]
283 def __getattr__(self, name):
287 raise AttributeError(e)
289 def __contains__(self, name):
290 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
292 def __hasattr__(self, name):
299 return "browse_record(%s, %d)" % (self._table_name, self._id)
301 def __eq__(self, other):
302 if not isinstance(other, browse_record):
304 return (self._table_name, self._id) == (other._table_name, other._id)
306 def __ne__(self, other):
307 if not isinstance(other, browse_record):
309 return (self._table_name, self._id) != (other._table_name, other._id)
311 # we need to define __unicode__ even though we've already defined __str__
312 # because we have overridden __getattr__
313 def __unicode__(self):
314 return unicode(str(self))
317 return hash((self._table_name, self._id))
325 (type returned by postgres when the column was created, type expression to create the column)
329 fields.boolean: 'bool',
330 fields.integer: 'int4',
331 fields.integer_big: 'int8',
335 fields.datetime: 'timestamp',
336 fields.binary: 'bytea',
337 fields.many2one: 'int4',
339 if type(f) in type_dict:
340 f_type = (type_dict[type(f)], type_dict[type(f)])
341 elif isinstance(f, fields.float):
343 f_type = ('numeric', 'NUMERIC')
345 f_type = ('float8', 'DOUBLE PRECISION')
346 elif isinstance(f, (fields.char, fields.reference)):
347 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
348 elif isinstance(f, fields.selection):
349 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
350 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
351 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
354 f_size = getattr(f, 'size', None) or 16
357 f_type = ('int4', 'INTEGER')
359 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
360 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
361 t = eval('fields.'+(f._type), globals())
362 f_type = (type_dict[t], type_dict[t])
363 elif isinstance(f, fields.function) and f._type == 'float':
365 f_type = ('numeric', 'NUMERIC')
367 f_type = ('float8', 'DOUBLE PRECISION')
368 elif isinstance(f, fields.function) and f._type == 'selection':
369 f_type = ('text', 'text')
370 elif isinstance(f, fields.function) and f._type == 'char':
371 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
373 logger = netsvc.Logger()
374 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
379 class orm_template(object):
385 _parent_name = 'parent_id'
386 _parent_store = False
387 _parent_order = False
397 CONCURRENCY_CHECK_FIELD = '__last_update'
398 def log(self, cr, uid, id, message, secondary=False, context=None):
399 return self.pool.get('res.log').create(cr, uid,
402 'res_model': self._name,
403 'secondary': secondary,
409 def view_init(self, cr, uid, fields_list, context=None):
410 """Override this method to do specific things when a view on the object is opened."""
413 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
414 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
416 def _field_create(self, cr, context=None):
419 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
421 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
422 model_id = cr.fetchone()[0]
423 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'))
425 model_id = cr.fetchone()[0]
426 if 'module' in context:
427 name_id = 'model_'+self._name.replace('.', '_')
428 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id, model_id, context['module']))
430 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
431 (name_id, context['module'], 'ir.model', model_id)
436 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
438 for rec in cr.dictfetchall():
439 cols[rec['name']] = rec
441 for (k, f) in self._columns.items():
443 'model_id': model_id,
446 'field_description': f.string.replace("'", " "),
448 'relation': f._obj or '',
449 'view_load': (f.view_load and 1) or 0,
450 'select_level': tools.ustr(f.select or 0),
451 'readonly': (f.readonly and 1) or 0,
452 'required': (f.required and 1) or 0,
453 'selectable': (f.selectable and 1) or 0,
454 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
456 # When its a custom field,it does not contain f.select
457 if context.get('field_state', 'base') == 'manual':
458 if context.get('field_name', '') == k:
459 vals['select_level'] = context.get('select', '0')
460 #setting value to let the problem NOT occur next time
462 vals['select_level'] = cols[k]['select_level']
465 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
466 id = cr.fetchone()[0]
468 cr.execute("""INSERT INTO ir_model_fields (
469 id, model_id, model, name, field_description, ttype,
470 relation,view_load,state,select_level,relation_field
472 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
474 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
475 vals['relation'], bool(vals['view_load']), 'base',
476 vals['select_level'], vals['relation_field']
478 if 'module' in context:
479 name1 = 'field_' + self._table + '_' + k
480 cr.execute("select name from ir_model_data where name=%s", (name1,))
482 name1 = name1 + "_" + str(id)
483 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
484 (name1, context['module'], 'ir.model.fields', id)
487 for key, val in vals.items():
488 if cols[k][key] != vals[key]:
489 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
491 cr.execute("""UPDATE ir_model_fields SET
492 model_id=%s, field_description=%s, ttype=%s, relation=%s,
493 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
495 model=%s AND name=%s""", (
496 vals['model_id'], vals['field_description'], vals['ttype'],
497 vals['relation'], bool(vals['view_load']),
498 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
503 def _auto_init(self, cr, context=None):
504 self._field_create(cr, context=context)
506 def __init__(self, cr):
507 if not self._name and not hasattr(self, '_inherit'):
508 name = type(self).__name__.split('.')[0]
509 msg = "The class %s has to have a _name attribute" % name
511 logger = netsvc.Logger()
512 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
513 raise except_orm('ValueError', msg)
515 if not self._description:
516 self._description = self._name
518 self._table = self._name.replace('.', '_')
520 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
522 Fetch records as objects allowing to use dot notation to browse fields and relations
524 :param cr: database cursor
525 :param user: current user id
526 :param select: id or list of ids
527 :param context: context arguments, like lang, time zone
528 :rtype: object or list of objects requested
531 self._list_class = list_class or browse_record_list
533 # need to accepts ints and longs because ids coming from a method
534 # launched by button in the interface have a type long...
535 if isinstance(select, (int, long)):
536 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
537 elif isinstance(select, list):
538 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)
542 def __export_row(self, cr, uid, row, fields, context=None):
546 def check_type(field_type):
547 if field_type == 'float':
549 elif field_type == 'integer':
551 elif field_type == 'boolean':
555 def selection_field(in_field):
556 col_obj = self.pool.get(in_field.keys()[0])
557 if f[i] in col_obj._columns.keys():
558 return col_obj._columns[f[i]]
559 elif f[i] in col_obj._inherits.keys():
560 selection_field(col_obj._inherits)
565 data = map(lambda x: '', range(len(fields)))
567 for fpos in range(len(fields)):
576 model_data = self.pool.get('ir.model.data')
577 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
579 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
581 r = '%s.%s' % (d['module'], d['name'])
588 # To display external name of selection field when its exported
590 if f[i] in self._columns.keys():
591 cols = self._columns[f[i]]
592 elif f[i] in self._inherit_fields.keys():
593 cols = selection_field(self._inherits)
594 if cols and cols._type == 'selection':
595 sel_list = cols.selection
596 if r and type(sel_list) == type([]):
597 r = [x[1] for x in sel_list if r==x[0]]
598 r = r and r[0] or False
600 if f[i] in self._columns:
601 r = check_type(self._columns[f[i]]._type)
602 elif f[i] in self._inherit_fields:
603 r = check_type(self._inherit_fields[f[i]][2]._type)
604 data[fpos] = r or False
606 if isinstance(r, (browse_record_list, list)):
608 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
611 if [x for x in fields2 if x]:
615 lines2 = self.__export_row(cr, uid, row2, fields2,
618 for fpos2 in range(len(fields)):
619 if lines2 and lines2[0][fpos2]:
620 data[fpos2] = lines2[0][fpos2]
624 if isinstance(rr.name, browse_record):
626 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
627 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
628 dt += tools.ustr(rr_name or '') + ','
638 if isinstance(r, browse_record):
639 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
640 r = r and r[0] and r[0][1] or ''
641 data[fpos] = tools.ustr(r or '')
642 return [data] + lines
644 def export_data(self, cr, uid, ids, fields_to_export, context=None):
646 Export fields for selected objects
648 :param cr: database cursor
649 :param uid: current user id
650 :param ids: list of ids
651 :param fields_to_export: list of fields
652 :param context: context arguments, like lang, time zone
653 :rtype: dictionary with a *datas* matrix
655 This method is used when exporting data via client menu
660 cols = self._columns.copy()
661 for f in self._inherit_fields:
662 cols.update({f: self._inherit_fields[f][2]})
664 if x=='.id': return [x]
665 return x.replace(':id','/id').replace('.id','/.id').split('/')
666 fields_to_export = map(fsplit, fields_to_export)
667 fields_export = fields_to_export + []
671 for row in self.browse(cr, uid, ids, context):
672 datas += self.__export_row(cr, uid, row, fields_to_export, context)
673 return {'datas': datas}
675 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
677 Import given data in given module
679 :param cr: database cursor
680 :param uid: current user id
681 :param fields: list of fields
682 :param data: data to import
683 :param mode: 'init' or 'update' for record creation
684 :param current_module: module name
685 :param noupdate: flag for record creation
686 :param context: context arguments, like lang, time zone,
687 :param filename: optional file to store partial import state for recovery
690 This method is used when importing data via client menu
691 Example of fields to import for a sale.order
693 partner_id, (=name_search)
694 order_line/.id, (=database_id)
696 order_line/product_id/id, (=xml id)
697 order_line/price_unit,
698 order_line/product_uom_qty,
699 order_line/product_uom/id (=xml_id)
703 def _replace_field(x):
704 x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
705 return x.replace(':id','/id').split('/')
706 fields = map(_replace_field, fields)
707 logger = netsvc.Logger()
708 ir_model_data_obj = self.pool.get('ir.model.data')
710 # mode: id (XML id) or .id (database id) or False for name_get
711 def _get_id(model_name, id, current_module=False, mode='id'):
714 obj_model = self.pool.get(model_name)
715 ids = obj_model.search(cr, uid, [('id', '=', int(id))])
717 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
720 module, xml_id = id.rsplit('.', 1)
722 module, xml_id = current_module, id
723 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
724 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
725 if not ir_model_data:
726 raise ValueError('No references to %s.%s' % (module, xml_id))
727 id = ir_model_data[0]['res_id']
729 obj_model = self.pool.get(model_name)
730 ids = obj_model.name_search(cr, uid, id)
732 raise ValueError('No record found for %s' % (id,))
737 # datas: a list of records, each record is defined by a list of values
738 # prefix: a list of prefix fields ['line_ids']
739 # position: the line to process, skip is False if it's the first line of the current record
741 # (res, position, warning, res_id) with
742 # res: the record for the next line to process (including it's one2many)
743 # position: the new position for the next line
744 # res_id: the ID of the record if it's a modification
745 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
746 line = datas[position]
753 for i in range(len(fields)):
758 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
761 if field[:len(prefix)] <> prefix:
766 # ID of the record using a XML ID
767 if field[len(prefix)]=='id':
768 data_res_id = _get_id(model_name, line[i], current_module, 'id')
771 # ID of the record using a database ID
772 elif field[len(prefix)]=='.id':
773 data_res_id = _get_id(model_name, line[i], current_module, '.id')
776 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
777 if fields_def[field[len(prefix)]]['type']=='one2many':
778 if field[len(prefix)] in done:
780 done[field[len(prefix)]] = True
781 relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
782 newfd = relation_obj.fields_get( cr, uid, context=context )
786 while pos < len(datas):
787 res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
790 (newrow, pos, w2, data_res_id2) = res2
791 nbrmax = max(nbrmax, pos)
794 if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
796 res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
798 elif fields_def[field[len(prefix)]]['type']=='many2one':
799 relation = fields_def[field[len(prefix)]]['relation']
800 if len(field) == len(prefix)+1:
803 mode = field[len(prefix)+1]
804 res = _get_id(relation, line[i], current_module, mode)
806 elif fields_def[field[len(prefix)]]['type']=='many2many':
807 relation = fields_def[field[len(prefix)]]['relation']
808 if len(field) == len(prefix)+1:
811 mode = field[len(prefix)+1]
813 # TODO: improve this by using csv.csv_reader
815 for db_id in line[i].split(config.get('csv_internal_sep')):
816 res.append( _get_id(relation, db_id, current_module, mode) )
819 elif fields_def[field[len(prefix)]]['type'] == 'integer':
820 res = line[i] and int(line[i]) or 0
821 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
822 res = line[i].lower() not in ('0', 'false', 'off')
823 elif fields_def[field[len(prefix)]]['type'] == 'float':
824 res = line[i] and float(line[i]) or 0.0
825 elif fields_def[field[len(prefix)]]['type'] == 'selection':
826 for key, val in fields_def[field[len(prefix)]]['selection']:
827 if line[i] in [tools.ustr(key), tools.ustr(val)]:
830 if line[i] and not res:
831 logger.notifyChannel("import", netsvc.LOG_WARNING,
832 _("key '%s' not found in selection field '%s'") % \
833 (line[i], field[len(prefix)]))
834 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
838 row[field[len(prefix)]] = res or False
840 result = (row, nbrmax, warning, data_res_id)
843 fields_def = self.fields_get(cr, uid, context=context)
845 if config.get('import_partial', False) and filename:
846 data = pickle.load(file(config.get('import_partial')))
847 original_value = data.get(filename, 0)
850 while position<len(datas):
853 (res, position, warning, res_id) = \
854 process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
857 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
860 id = ir_model_data_obj._update(cr, uid, self._name,
861 current_module, res, mode=mode,
862 noupdate=noupdate, res_id=res_id, context=context)
864 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
866 if config.get('import_partial', False) and filename and (not (position%100)):
867 data = pickle.load(file(config.get('import_partial')))
868 data[filename] = position
869 pickle.dump(data, file(config.get('import_partial'), 'wb'))
870 if context.get('defer_parent_store_computation'):
871 self._parent_store_compute(cr)
874 if context.get('defer_parent_store_computation'):
875 self._parent_store_compute(cr)
876 return (position, 0, 0, 0)
878 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
880 Read records with given ids with the given fields
882 :param cr: database cursor
883 :param user: current user id
884 :param ids: id or list of the ids of the records to read
885 :param fields: optional list of field names to return (default: all fields would be returned)
886 :type fields: list (example ['field_name_1', ...])
887 :param context: optional context dictionary - it may contains keys for specifying certain options
888 like ``context_lang``, ``context_tz`` to alter the results of the call.
889 A special ``bin_size`` boolean flag may also be passed in the context to request the
890 value of all fields.binary columns to be returned as the size of the binary instead of its
891 contents. This can also be selectively overriden by passing a field-specific flag
892 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
893 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
894 :return: list of dictionaries((dictionary per record asked)) with requested field values
895 :rtype: [{‘name_of_the_field’: value, ...}, ...]
896 :raise AccessError: * if user has no read rights on the requested object
897 * if user tries to bypass access rules for read on the requested object
900 raise NotImplementedError(_('The read method is not implemented on this object !'))
902 def get_invalid_fields(self, cr, uid):
903 return list(self._invalids)
905 def _validate(self, cr, uid, ids, context=None):
906 context = context or {}
907 lng = context.get('lang', False) or 'en_US'
908 trans = self.pool.get('ir.translation')
910 for constraint in self._constraints:
911 fun, msg, fields = constraint
912 if not fun(self, cr, uid, ids):
913 # Check presence of __call__ directly instead of using
914 # callable() because it will be deprecated as of Python 3.0
915 if hasattr(msg, '__call__'):
916 tmp_msg = msg(self, cr, uid, ids, context=context)
917 if isinstance(tmp_msg, tuple):
918 tmp_msg, params = tmp_msg
919 translated_msg = tmp_msg % params
921 translated_msg = tmp_msg
923 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
925 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
927 self._invalids.update(fields)
930 raise except_orm('ValidateError', '\n'.join(error_msgs))
932 self._invalids.clear()
934 def default_get(self, cr, uid, fields_list, context=None):
936 Returns default values for the fields in fields_list.
938 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
939 :type fields_list: list
940 :param context: optional context dictionary - it may contains keys for specifying certain options
941 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
942 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
943 or override a default value for a field.
944 A special ``bin_size`` boolean flag may also be passed in the context to request the
945 value of all fields.binary columns to be returned as the size of the binary instead of its
946 contents. This can also be selectively overriden by passing a field-specific flag
947 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
948 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
949 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
951 # trigger view init hook
952 self.view_init(cr, uid, fields_list, context)
958 # get the default values for the inherited fields
959 for t in self._inherits.keys():
960 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
963 # get the default values defined in the object
964 for f in fields_list:
965 if f in self._defaults:
966 if callable(self._defaults[f]):
967 defaults[f] = self._defaults[f](self, cr, uid, context)
969 defaults[f] = self._defaults[f]
971 fld_def = ((f in self._columns) and self._columns[f]) \
972 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
975 if isinstance(fld_def, fields.property):
976 property_obj = self.pool.get('ir.property')
977 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
979 if isinstance(prop_value, (browse_record, browse_null)):
980 defaults[f] = prop_value.id
982 defaults[f] = prop_value
984 if f not in defaults:
987 # get the default values set by the user and override the default
988 # values defined in the object
989 ir_values_obj = self.pool.get('ir.values')
990 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
991 for id, field, field_value in res:
992 if field in fields_list:
993 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
994 if fld_def._type in ('many2one', 'one2one'):
995 obj = self.pool.get(fld_def._obj)
996 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
998 if fld_def._type in ('many2many'):
999 obj = self.pool.get(fld_def._obj)
1001 for i in range(len(field_value)):
1002 if not obj.search(cr, uid, [('id', '=',
1005 field_value2.append(field_value[i])
1006 field_value = field_value2
1007 if fld_def._type in ('one2many'):
1008 obj = self.pool.get(fld_def._obj)
1010 for i in range(len(field_value)):
1011 field_value2.append({})
1012 for field2 in field_value[i]:
1013 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1014 obj2 = self.pool.get(obj._columns[field2]._obj)
1015 if not obj2.search(cr, uid,
1016 [('id', '=', field_value[i][field2])]):
1018 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1019 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1020 if not obj2.search(cr, uid,
1021 [('id', '=', field_value[i][field2])]):
1023 # TODO add test for many2many and one2many
1024 field_value2[i][field2] = field_value[i][field2]
1025 field_value = field_value2
1026 defaults[field] = field_value
1028 # get the default values from the context
1029 for key in context or {}:
1030 if key.startswith('default_') and (key[8:] in fields_list):
1031 defaults[key[8:]] = context[key]
1035 def perm_read(self, cr, user, ids, context=None, details=True):
1036 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1038 def unlink(self, cr, uid, ids, context=None):
1039 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1041 def write(self, cr, user, ids, vals, context=None):
1042 raise NotImplementedError(_('The write method is not implemented on this object !'))
1044 def create(self, cr, user, vals, context=None):
1045 raise NotImplementedError(_('The create method is not implemented on this object !'))
1047 def fields_get_keys(self, cr, user, context=None):
1048 res = self._columns.keys()
1049 for parent in self._inherits:
1050 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1053 # returns the definition of each field in the object
1054 # the optional fields parameter can limit the result to some fields
1055 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1059 translation_obj = self.pool.get('ir.translation')
1060 for parent in self._inherits:
1061 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1063 if self._columns.keys():
1064 for f in self._columns.keys():
1065 field_col = self._columns[f]
1066 if allfields and f not in allfields:
1068 res[f] = {'type': field_col._type}
1069 # This additional attributes for M2M and function field is added
1070 # because we need to display tooltip with this additional information
1071 # when client is started in debug mode.
1072 if isinstance(field_col, fields.function):
1073 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1074 res[f]['store'] = field_col.store
1075 if isinstance(field_col.store, dict):
1076 res[f]['store'] = str(field_col.store)
1077 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1078 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1079 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1080 res[f]['func_obj'] = field_col._obj or False
1081 res[f]['func_method'] = field_col._method
1082 if isinstance(field_col, fields.many2many):
1083 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1084 res[f]['third_table'] = field_col._rel
1085 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1086 'change_default', 'translate', 'help', 'select', 'selectable'):
1087 if getattr(field_col, arg):
1088 res[f][arg] = getattr(field_col, arg)
1089 if not write_access:
1090 res[f]['readonly'] = True
1091 res[f]['states'] = {}
1092 for arg in ('digits', 'invisible', 'filters'):
1093 if getattr(field_col, arg, None):
1094 res[f][arg] = getattr(field_col, arg)
1096 if field_col.string:
1097 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1099 res[f]['string'] = res_trans
1101 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1103 res[f]['help'] = help_trans
1105 if hasattr(field_col, 'selection'):
1106 if isinstance(field_col.selection, (tuple, list)):
1107 sel = field_col.selection
1108 # translate each selection option
1110 for (key, val) in sel:
1113 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1114 sel2.append((key, val2 or val))
1116 res[f]['selection'] = sel
1118 # call the 'dynamic selection' function
1119 res[f]['selection'] = field_col.selection(self, cr, user, context)
1120 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1121 res[f]['relation'] = field_col._obj
1122 res[f]['domain'] = field_col._domain
1123 res[f]['context'] = field_col._context
1125 #TODO : read the fields from the database
1129 # filter out fields which aren't in the fields list
1130 for r in res.keys():
1131 if r not in allfields:
1136 # Overload this method if you need a window title which depends on the context
1138 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1141 def __view_look_dom(self, cr, user, node, view_id, context=None):
1149 if isinstance(s, unicode):
1150 return s.encode('utf8')
1153 # return True if node can be displayed to current user
1154 def check_group(node):
1155 if node.get('groups'):
1156 groups = node.get('groups').split(',')
1157 access_pool = self.pool.get('ir.model.access')
1158 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1160 node.set('invisible', '1')
1161 if 'attrs' in node.attrib:
1162 del(node.attrib['attrs']) #avoid making field visible later
1163 del(node.attrib['groups'])
1168 if node.tag in ('field', 'node', 'arrow'):
1169 if node.get('object'):
1174 if f.tag in ('field'):
1175 xml += etree.tostring(f, encoding="utf-8")
1177 new_xml = etree.fromstring(encode(xml))
1178 ctx = context.copy()
1179 ctx['base_model_name'] = self._name
1180 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1185 attrs = {'views': views}
1187 if node.get('name'):
1190 if node.get('name') in self._columns:
1191 column = self._columns[node.get('name')]
1193 column = self._inherit_fields[node.get('name')][2]
1198 relation = self.pool.get(column._obj)
1203 if f.tag in ('form', 'tree', 'graph'):
1205 ctx = context.copy()
1206 ctx['base_model_name'] = self._name
1207 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1208 views[str(f.tag)] = {
1212 attrs = {'views': views}
1213 if node.get('widget') and node.get('widget') == 'selection':
1214 # Prepare the cached selection list for the client. This needs to be
1215 # done even when the field is invisible to the current user, because
1216 # other events could need to change its value to any of the selectable ones
1217 # (such as on_change events, refreshes, etc.)
1219 # If domain and context are strings, we keep them for client-side, otherwise
1220 # we evaluate them server-side to consider them when generating the list of
1222 # TODO: find a way to remove this hack, by allow dynamic domains
1224 if column._domain and not isinstance(column._domain, basestring):
1225 dom = column._domain
1226 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1227 search_context = dict(context)
1228 if column._context and not isinstance(column._context, basestring):
1229 search_context.update(column._context)
1230 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1231 if (node.get('required') and not int(node.get('required'))) or not column.required:
1232 attrs['selection'].append((False, ''))
1233 fields[node.get('name')] = attrs
1235 elif node.tag in ('form', 'tree'):
1236 result = self.view_header_get(cr, user, False, node.tag, context)
1238 node.set('string', result)
1240 elif node.tag == 'calendar':
1241 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1242 if node.get(additional_field):
1243 fields[node.get(additional_field)] = {}
1245 if 'groups' in node.attrib:
1249 if ('lang' in context) and not result:
1250 if node.get('string'):
1251 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1252 if trans == node.get('string') and ('base_model_name' in context):
1253 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1254 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1255 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1257 node.set('string', trans)
1258 if node.get('confirm'):
1259 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1261 node.set('confirm', trans)
1263 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1265 node.set('sum', trans)
1268 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1269 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1273 def _disable_workflow_buttons(self, cr, user, node):
1275 # admin user can always activate workflow buttons
1278 # TODO handle the case of more than one workflow for a model or multiple
1279 # transitions with different groups and same signal
1280 usersobj = self.pool.get('res.users')
1281 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1282 for button in buttons:
1283 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1284 cr.execute("""SELECT DISTINCT t.group_id
1286 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1287 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1290 AND t.group_id is NOT NULL
1291 """, (self._name, button.get('name')))
1292 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1293 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1294 button.set('readonly', str(int(not can_click)))
1297 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1298 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1299 node = self._disable_workflow_buttons(cr, user, node)
1300 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1302 if node.tag == 'diagram':
1303 if node.getchildren()[0].tag == 'node':
1304 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1305 if node.getchildren()[1].tag == 'arrow':
1306 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1307 for key, value in node_fields.items():
1309 for key, value in arrow_fields.items():
1312 fields = self.fields_get(cr, user, fields_def.keys(), context)
1313 for field in fields_def:
1315 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1316 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1317 elif field in fields:
1318 fields[field].update(fields_def[field])
1320 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))
1321 res = cr.fetchall()[:]
1323 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1324 msg = "\n * ".join([r[0] for r in res])
1325 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1326 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1327 raise except_orm('View error', msg)
1330 def __get_default_calendar_view(self):
1331 """Generate a default calendar view (For internal use only).
1334 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1335 '<calendar string="%s"') % (self._description)
1337 if (self._date_name not in self._columns):
1339 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1340 if dt in self._columns:
1341 self._date_name = dt
1346 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1349 arch += ' date_start="%s"' % (self._date_name)
1351 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1352 if color in self._columns:
1353 arch += ' color="' + color + '"'
1356 dt_stop_flag = False
1358 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1359 if dt_stop in self._columns:
1360 arch += ' date_stop="' + dt_stop + '"'
1364 if not dt_stop_flag:
1365 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1366 if dt_delay in self._columns:
1367 arch += ' date_delay="' + dt_delay + '"'
1371 ' <field name="%s"/>\n'
1372 '</calendar>') % (self._rec_name)
1376 def __get_default_search_view(self, cr, uid, context=None):
1377 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1378 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1380 fields_to_search = set()
1381 fields = self.fields_get(cr, uid, context=context)
1382 for field in fields:
1383 if fields[field].get('select'):
1384 fields_to_search.add(field)
1385 for view in (form_view, tree_view):
1386 view_root = etree.fromstring(view['arch'])
1387 # Only care about select=1 in xpath below, because select=2 is covered
1388 # by the custom advanced search in clients
1389 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1391 tree_view_root = view_root # as provided by loop above
1392 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1393 field_group = etree.Element("group")
1394 search_view.append(field_group)
1396 for field_name in fields_to_search:
1397 field_group.append(etree.Element("field", attrib={'name': field_name}))
1399 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1402 # if view_id, view_type is not required
1404 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1406 Get the detailed composition of the requested view like fields, model, view architecture
1408 :param cr: database cursor
1409 :param user: current user id
1410 :param view_id: id of the view or None
1411 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1412 :param context: context arguments, like lang, time zone
1413 :param toolbar: true to include contextual actions
1414 :param submenu: example (portal_project module)
1415 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1416 :raise AttributeError:
1417 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1418 * if some tag other than 'position' is found in parent view
1419 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1426 if isinstance(s, unicode):
1427 return s.encode('utf8')
1430 def raise_view_error(error_msg, child_view_id):
1431 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1432 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1433 % { 'xml_id': child_view.xml_id,
1434 'parent_xml_id': view.xml_id,
1435 'model': self._name, })
1437 def _inherit_apply(src, inherit, inherit_id=None):
1438 def _find(node, node2):
1439 if node2.tag == 'xpath':
1440 res = node.xpath(node2.get('expr'))
1446 for n in node.getiterator(node2.tag):
1448 if node2.tag == 'field':
1449 # only compare field names, a field can be only once in a given view
1450 # at a given level (and for multilevel expressions, we should use xpath
1451 # inheritance spec anyway)
1452 if node2.get('name') == n.get('name'):
1456 for attr in node2.attrib:
1457 if attr == 'position':
1460 if n.get(attr) == node2.get(attr):
1467 # End: _find(node, node2)
1469 doc_dest = etree.fromstring(encode(inherit))
1470 toparse = [doc_dest]
1473 node2 = toparse.pop(0)
1474 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1476 if node2.tag == 'data':
1477 toparse += [ c for c in doc_dest ]
1479 node = _find(src, node2)
1480 if node is not None:
1482 if node2.get('position'):
1483 pos = node2.get('position')
1484 if pos == 'replace':
1485 parent = node.getparent()
1487 src = copy.deepcopy(node2[0])
1490 node.addprevious(child)
1491 node.getparent().remove(node)
1492 elif pos == 'attributes':
1493 for child in node2.getiterator('attribute'):
1494 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1496 node.set(attribute[0], attribute[1])
1498 del(node.attrib[attribute[0]])
1500 sib = node.getnext()
1504 elif pos == 'after':
1509 sib.addprevious(child)
1510 elif pos == 'before':
1511 node.addprevious(child)
1513 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1516 ' %s="%s"' % (attr, node2.get(attr))
1517 for attr in node2.attrib
1518 if attr != 'position'
1520 tag = "<%s%s>" % (node2.tag, attrs)
1521 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1523 # End: _inherit_apply(src, inherit)
1525 result = {'type': view_type, 'model': self._name}
1530 parent_view_model = None
1532 view_ref = context.get(view_type + '_view_ref', False)
1533 if view_ref and not view_id:
1535 module, view_ref = view_ref.split('.', 1)
1536 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1537 view_ref_res = cr.fetchone()
1539 view_id = view_ref_res[0]
1542 query = "SELECT arch,name,field_parent,id,type,inherit_id,model FROM ir_ui_view WHERE id=%s"
1545 query += " AND model=%s"
1546 params += (self._name,)
1547 cr.execute(query, params)
1549 cr.execute('''SELECT
1550 arch,name,field_parent,id,type,inherit_id,model
1557 ORDER BY priority''', (self._name, view_type))
1558 sql_res = cr.fetchone()
1564 view_id = ok or sql_res[3]
1566 parent_view_model = sql_res[6]
1568 # if a view was found
1570 result['type'] = sql_res[4]
1571 result['view_id'] = sql_res[3]
1572 result['arch'] = sql_res[0]
1574 def _inherit_apply_rec(result, inherit_id):
1575 # get all views which inherit from (ie modify) this view
1576 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1577 sql_inherit = cr.fetchall()
1578 for (inherit, id) in sql_inherit:
1579 result = _inherit_apply(result, inherit, id)
1580 result = _inherit_apply_rec(result, id)
1583 inherit_result = etree.fromstring(encode(result['arch']))
1584 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1586 result['name'] = sql_res[1]
1587 result['field_parent'] = sql_res[2] or False
1590 # otherwise, build some kind of default view
1591 if view_type == 'form':
1592 res = self.fields_get(cr, user, context=context)
1593 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1594 '<form string="%s">' % (self._description,)
1596 if res[x]['type'] not in ('one2many', 'many2many'):
1597 xml += '<field name="%s"/>' % (x,)
1598 if res[x]['type'] == 'text':
1602 elif view_type == 'tree':
1603 _rec_name = self._rec_name
1604 if _rec_name not in self._columns:
1605 _rec_name = self._columns.keys()[0]
1606 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1607 '<tree string="%s"><field name="%s"/></tree>' \
1608 % (self._description, self._rec_name)
1610 elif view_type == 'calendar':
1611 xml = self.__get_default_calendar_view()
1613 elif view_type == 'search':
1614 xml = self.__get_default_search_view(cr, user, context)
1617 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1618 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1619 result['arch'] = etree.fromstring(encode(xml))
1620 result['name'] = 'default'
1621 result['field_parent'] = False
1622 result['view_id'] = 0
1624 if parent_view_model != self._name:
1625 ctx = context.copy()
1626 ctx['base_model_name'] = parent_view_model
1629 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1630 result['arch'] = xarch
1631 result['fields'] = xfields
1634 if context and context.get('active_id', False):
1635 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1637 act_id = data_menu.id
1639 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1640 result['submenu'] = getattr(data_action, 'menus', False)
1644 for key in ('report_sxw_content', 'report_rml_content',
1645 'report_sxw', 'report_rml',
1646 'report_sxw_content_data', 'report_rml_content_data'):
1650 ir_values_obj = self.pool.get('ir.values')
1651 resprint = ir_values_obj.get(cr, user, 'action',
1652 'client_print_multi', [(self._name, False)], False,
1654 resaction = ir_values_obj.get(cr, user, 'action',
1655 'client_action_multi', [(self._name, False)], False,
1658 resrelate = ir_values_obj.get(cr, user, 'action',
1659 'client_action_relate', [(self._name, False)], False,
1661 resprint = map(clean, resprint)
1662 resaction = map(clean, resaction)
1663 resaction = filter(lambda x: not x.get('multi', False), resaction)
1664 resprint = filter(lambda x: not x.get('multi', False), resprint)
1665 resrelate = map(lambda x: x[2], resrelate)
1667 for x in resprint + resaction + resrelate:
1668 x['string'] = x['name']
1670 result['toolbar'] = {
1672 'action': resaction,
1677 _view_look_dom_arch = __view_look_dom_arch
1679 def search_count(self, cr, user, args, context=None):
1682 res = self.search(cr, user, args, context=context, count=True)
1683 if isinstance(res, list):
1687 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1689 Search for records based on a search domain.
1691 :param cr: database cursor
1692 :param user: current user id
1693 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1694 :param offset: optional number of results to skip in the returned values (default: 0)
1695 :param limit: optional max number of records to return (default: **None**)
1696 :param order: optional columns to sort by (default: self._order=id )
1697 :param context: optional context arguments, like lang, time zone
1698 :type context: dictionary
1699 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1700 :return: id or list of ids of records matching the criteria
1701 :rtype: integer or list of integers
1702 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1704 **Expressing a search domain (args)**
1706 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1708 * **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.
1709 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1710 The semantics of most of these operators are obvious.
1711 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1712 according to the semantics of this model (i.e following the relationship field named by
1713 ``self._parent_name``, by default ``parent_id``.
1714 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1716 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1717 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1718 Be very careful about this when you combine them the first time.
1720 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1722 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1724 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::
1726 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1729 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1731 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1733 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1734 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1735 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1737 :param access_rights_uid: optional user ID to use when checking access rights
1738 (not for ir.rules, this is only for ir.model.access)
1740 raise NotImplementedError(_('The search method is not implemented on this object !'))
1742 def name_get(self, cr, user, ids, context=None):
1745 :param cr: database cursor
1746 :param user: current user id
1748 :param ids: list of ids
1749 :param context: context arguments, like lang, time zone
1750 :type context: dictionary
1751 :return: tuples with the text representation of requested objects for to-many relationships
1758 if isinstance(ids, (int, long)):
1760 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1761 [self._rec_name], context, load='_classic_write')]
1763 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1765 Search for records and their display names according to a search domain.
1767 :param cr: database cursor
1768 :param user: current user id
1769 :param name: object name to search
1770 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1771 :param operator: operator for search criterion
1772 :param context: context arguments, like lang, time zone
1773 :type context: dictionary
1774 :param limit: optional max number of records to return
1775 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1777 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1778 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1781 return self._name_search(cr, user, name, args, operator, context, limit)
1783 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1784 # solve some access rights issues
1785 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1792 args += [(self._rec_name, operator, name)]
1793 access_rights_uid = name_get_uid or user
1794 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1795 res = self.name_get(cr, access_rights_uid, ids, context)
1798 def copy(self, cr, uid, id, default=None, context=None):
1799 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1801 def exists(self, cr, uid, id, context=None):
1802 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1804 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1807 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1809 fields = self._columns.keys() + self._inherit_fields.keys()
1810 #FIXME: collect all calls to _get_source into one SQL call.
1812 res[lang] = {'code': lang}
1814 if f in self._columns:
1815 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1817 res[lang][f] = res_trans
1819 res[lang][f] = self._columns[f].string
1820 for table in self._inherits:
1821 cols = intersect(self._inherit_fields.keys(), fields)
1822 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1825 res[lang]['code'] = lang
1826 for f in res2[lang]:
1827 res[lang][f] = res2[lang][f]
1830 def write_string(self, cr, uid, id, langs, vals, context=None):
1831 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1832 #FIXME: try to only call the translation in one SQL
1835 if field in self._columns:
1836 src = self._columns[field].string
1837 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1838 for table in self._inherits:
1839 cols = intersect(self._inherit_fields.keys(), vals)
1841 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1844 def _check_removed_columns(self, cr, log=False):
1845 raise NotImplementedError()
1847 def _add_missing_default_values(self, cr, uid, values, context=None):
1848 missing_defaults = []
1849 avoid_tables = [] # avoid overriding inherited values when parent is set
1850 for tables, parent_field in self._inherits.items():
1851 if parent_field in values:
1852 avoid_tables.append(tables)
1853 for field in self._columns.keys():
1854 if not field in values:
1855 missing_defaults.append(field)
1856 for field in self._inherit_fields.keys():
1857 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1858 missing_defaults.append(field)
1860 if len(missing_defaults):
1861 # override defaults with the provided values, never allow the other way around
1862 defaults = self.default_get(cr, uid, missing_defaults, context)
1864 if (dv in self._columns and self._columns[dv]._type == 'many2many') \
1865 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many') \
1866 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1867 defaults[dv] = [(6, 0, defaults[dv])]
1868 if dv in self._columns and self._columns[dv]._type == 'one2many' \
1869 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many') \
1870 and isinstance(defaults[dv], (list, tuple)) and isinstance(defaults[dv][0], dict):
1871 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
1872 defaults.update(values)
1876 class orm_memory(orm_template):
1878 _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']
1879 _inherit_fields = {}
1884 def __init__(self, cr):
1885 super(orm_memory, self).__init__(cr)
1889 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1891 def _check_access(self, uid, object_id, mode):
1892 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1893 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1895 def vaccum(self, cr, uid):
1897 if self.check_id % self._check_time:
1900 max = time.time() - self._max_hours * 60 * 60
1901 for id in self.datas:
1902 if self.datas[id]['internal.date_access'] < max:
1904 self.unlink(cr, 1, tounlink)
1905 if len(self.datas) > self._max_count:
1906 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1908 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1909 self.unlink(cr, uid, ids)
1912 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1915 if not fields_to_read:
1916 fields_to_read = self._columns.keys()
1920 if isinstance(ids, (int, long)):
1924 for f in fields_to_read:
1925 record = self.datas.get(id)
1927 self._check_access(user, id, 'read')
1928 r[f] = record.get(f, False)
1929 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1932 if id in self.datas:
1933 self.datas[id]['internal.date_access'] = time.time()
1934 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1935 for f in fields_post:
1936 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1937 for record in result:
1938 record[f] = res2[record['id']]
1939 if isinstance(ids_orig, (int, long)):
1943 def write(self, cr, user, ids, vals, context=None):
1949 if self._columns[field]._classic_write:
1950 vals2[field] = vals[field]
1952 upd_todo.append(field)
1953 for object_id in ids:
1954 self._check_access(user, object_id, mode='write')
1955 self.datas[object_id].update(vals2)
1956 self.datas[object_id]['internal.date_access'] = time.time()
1957 for field in upd_todo:
1958 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
1959 self._validate(cr, user, [object_id], context)
1960 wf_service = netsvc.LocalService("workflow")
1961 wf_service.trg_write(user, self._name, object_id, cr)
1964 def create(self, cr, user, vals, context=None):
1965 self.vaccum(cr, user)
1967 id_new = self.next_id
1969 vals = self._add_missing_default_values(cr, user, vals, context)
1974 if self._columns[field]._classic_write:
1975 vals2[field] = vals[field]
1977 upd_todo.append(field)
1978 self.datas[id_new] = vals2
1979 self.datas[id_new]['internal.date_access'] = time.time()
1980 self.datas[id_new]['internal.create_uid'] = user
1982 for field in upd_todo:
1983 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1984 self._validate(cr, user, [id_new], context)
1985 if self._log_create and not (context and context.get('no_store_function', False)):
1986 message = self._description + \
1988 self.name_get(cr, user, [id_new], context=context)[0][1] + \
1990 self.log(cr, user, id_new, message, True, context=context)
1991 wf_service = netsvc.LocalService("workflow")
1992 wf_service.trg_create(user, self._name, id_new, cr)
1995 def _where_calc(self, cr, user, args, active_test=True, context=None):
2000 # if the object has a field named 'active', filter out all inactive
2001 # records unless they were explicitely asked for
2002 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2004 active_in_args = False
2006 if a[0] == 'active':
2007 active_in_args = True
2008 if not active_in_args:
2009 args.insert(0, ('active', '=', 1))
2011 args = [('active', '=', 1)]
2014 e = expression.expression(args)
2015 e.parse(cr, user, self, context)
2019 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2023 # implicit filter on current user except for superuser
2027 args.insert(0, ('internal.create_uid', '=', user))
2029 result = self._where_calc(cr, user, args, context=context)
2031 return self.datas.keys()
2035 #Find the value of dict
2038 for id, data in self.datas.items():
2039 counter = counter + 1
2041 if limit and (counter > int(limit)):
2046 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2047 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2048 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2049 elif arg[1] in ['ilike']:
2050 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2060 def unlink(self, cr, uid, ids, context=None):
2062 self._check_access(uid, id, 'unlink')
2063 self.datas.pop(id, None)
2065 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2068 def perm_read(self, cr, user, ids, context=None, details=True):
2070 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2071 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2073 self._check_access(user, id, 'read')
2075 'create_uid': credentials,
2076 'create_date': create_date,
2078 'write_date': False,
2084 def _check_removed_columns(self, cr, log=False):
2085 # nothing to check in memory...
2088 def exists(self, cr, uid, id, context=None):
2089 return id in self.datas
2091 class orm(orm_template):
2092 _sql_constraints = []
2094 _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']
2095 __logger = logging.getLogger('orm')
2096 __schema = logging.getLogger('orm.schema')
2097 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2099 Get the list of records in list view grouped by the given ``groupby`` fields
2101 :param cr: database cursor
2102 :param uid: current user id
2103 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2104 :param fields: list of fields present in the list view specified on the object
2105 :param groupby: list of fields on which to groupby the records
2106 :type fields_list: list (example ['field_name_1', ...])
2107 :param offset: optional number of records to skip
2108 :param limit: optional max number of records to return
2109 :param context: context arguments, like lang, time zone
2110 :param order: optional ``order by`` specification, for overriding the natural
2111 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2112 (supported only for many2one fields currently)
2113 :return: list of dictionaries(one dictionary for each record) containing:
2115 * the values of fields grouped by the fields in ``groupby`` argument
2116 * __domain: list of tuples specifying the search criteria
2117 * __context: dictionary with argument like ``groupby``
2118 :rtype: [{'field_name_1': value, ...]
2119 :raise AccessError: * if user has no read rights on the requested object
2120 * if user tries to bypass access rules for read on the requested object
2123 context = context or {}
2124 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2126 fields = self._columns.keys()
2128 query = self._where_calc(cr, uid, domain, context=context)
2129 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2131 # Take care of adding join(s) if groupby is an '_inherits'ed field
2132 groupby_list = groupby
2134 if isinstance(groupby, list):
2135 groupby = groupby[0]
2136 self._inherits_join_calc(groupby, query)
2139 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?)"
2140 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2141 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"
2143 fget = self.fields_get(cr, uid, fields)
2144 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2146 group_count = group_by = groupby
2148 if fget.get(groupby):
2149 if fget[groupby]['type'] in ('date', 'datetime'):
2150 flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
2151 groupby = "to_char(%s,'yyyy-mm')" % (groupby)
2155 # Don't allow arbitrary values, as this would be a SQL injection vector!
2156 raise except_orm(_('Invalid group_by'),
2157 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2160 fields_pre = [f for f in float_int_fields if
2161 f == self.CONCURRENCY_CHECK_FIELD
2162 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2163 for f in fields_pre:
2164 if f not in ['id', 'sequence']:
2165 group_operator = fget[f].get('group_operator', 'sum')
2168 flist += group_operator+'('+f+') as '+f
2170 gb = groupby and (' GROUP BY '+groupby) or ''
2172 from_clause, where_clause, where_clause_params = query.get_sql()
2173 where_clause = where_clause and ' WHERE ' + where_clause
2174 limit_str = limit and ' limit %d' % limit or ''
2175 offset_str = offset and ' offset %d' % offset or ''
2176 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2178 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)
2181 for r in cr.dictfetchall():
2182 for fld, val in r.items():
2183 if val == None: r[fld] = False
2184 alldata[r['id']] = r
2187 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2188 # the IDS of records that have groupby field value = False or '' should be sorted too
2189 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2190 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2191 # 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):
2192 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2196 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2197 if not isinstance(groupby_list, (str, unicode)):
2198 if groupby or not context.get('group_by_no_leaf', False):
2199 d['__context'] = {'group_by': groupby_list[1:]}
2200 if groupby and groupby in fget:
2201 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2202 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2203 days = calendar.monthrange(dt.year, dt.month)[1]
2205 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2206 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),\
2207 (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
2208 del alldata[d['id']][groupby]
2209 d.update(alldata[d['id']])
2213 def _inherits_join_add(self, parent_model_name, query):
2215 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2217 :param parent_model_name: name of the parent model for which the clauses should be added
2218 :param query: query object on which the JOIN should be added
2220 inherits_field = self._inherits[parent_model_name]
2221 parent_model = self.pool.get(parent_model_name)
2222 parent_table_name = parent_model._table
2223 quoted_parent_table_name = '"%s"' % parent_table_name
2224 if quoted_parent_table_name not in query.tables:
2225 query.tables.append(quoted_parent_table_name)
2226 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2228 def _inherits_join_calc(self, field, query):
2230 Adds missing table select and join clause(s) to ``query`` for reaching
2231 the field coming from an '_inherits' parent table (no duplicates).
2233 :param field: name of inherited field to reach
2234 :param query: query object on which the JOIN should be added
2235 :return: qualified name of field, to be used in SELECT clause
2237 current_table = self
2238 while field in current_table._inherit_fields and not field in current_table._columns:
2239 parent_model_name = current_table._inherit_fields[field][0]
2240 parent_table = self.pool.get(parent_model_name)
2241 self._inherits_join_add(parent_model_name, query)
2242 current_table = parent_table
2243 return '"%s".%s' % (current_table._table, field)
2245 def _parent_store_compute(self, cr):
2246 if not self._parent_store:
2248 logger = netsvc.Logger()
2249 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2250 def browse_rec(root, pos=0):
2252 where = self._parent_name+'='+str(root)
2254 where = self._parent_name+' IS NULL'
2255 if self._parent_order:
2256 where += ' order by '+self._parent_order
2257 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2259 for id in cr.fetchall():
2260 pos2 = browse_rec(id[0], pos2)
2261 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2263 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2264 if self._parent_order:
2265 query += ' order by ' + self._parent_order
2268 for (root,) in cr.fetchall():
2269 pos = browse_rec(root, pos)
2272 def _update_store(self, cr, f, k):
2273 logger = netsvc.Logger()
2274 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2275 ss = self._columns[k]._symbol_set
2276 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2277 cr.execute('select id from '+self._table)
2278 ids_lst = map(lambda x: x[0], cr.fetchall())
2281 ids_lst = ids_lst[40:]
2282 res = f.get(cr, self, iids, k, 1, {})
2283 for key, val in res.items():
2286 # if val is a many2one, just write the ID
2287 if type(val) == tuple:
2289 if (val<>False) or (type(val)<>bool):
2290 cr.execute(update_query, (ss[1](val), key))
2292 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2293 """Raise except_orm if value is not among the valid values for the selection field"""
2294 if self._columns[field]._type == 'reference':
2295 val_model, val_id_str = value.split(',', 1)
2298 val_id = long(val_id_str)
2302 raise except_orm(_('ValidateError'),
2303 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2307 if isinstance(self._columns[field].selection, (tuple, list)):
2308 if val in dict(self._columns[field].selection):
2310 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2312 raise except_orm(_('ValidateError'),
2313 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2315 def _check_removed_columns(self, cr, log=False):
2316 # iterate on the database columns to drop the NOT NULL constraints
2317 # of fields which were required but have been removed (or will be added by another module)
2318 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2319 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2320 cr.execute("SELECT a.attname, a.attnotnull"
2321 " FROM pg_class c, pg_attribute a"
2322 " WHERE c.relname=%s"
2323 " AND c.oid=a.attrelid"
2324 " AND a.attisdropped=%s"
2325 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2326 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2328 for column in cr.dictfetchall():
2330 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2331 column['attname'], self._table, self._name)
2332 if column['attnotnull']:
2333 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2334 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2335 self._table, column['attname'])
2337 def _auto_init(self, cr, context=None):
2340 store_compute = False
2343 self._field_create(cr, context=context)
2344 if getattr(self, '_auto', True):
2345 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2347 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2348 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2350 self.__schema.debug("Table '%s': created", self._table)
2353 if self._parent_store:
2354 cr.execute("""SELECT c.relname
2355 FROM pg_class c, pg_attribute a
2356 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2357 """, (self._table, 'parent_left'))
2359 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2360 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2361 if 'parent_left' not in self._columns:
2362 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2364 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2365 self._table, 'parent_left', 'INTEGER')
2366 elif not self._columns['parent_left'].select:
2367 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2369 if 'parent_right' not in self._columns:
2370 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2372 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2373 self._table, 'parent_right', 'INTEGER')
2374 elif not self._columns['parent_right'].select:
2375 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2377 if self._columns[self._parent_name].ondelete != 'cascade':
2378 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2379 self._parent_name, self._name)
2382 store_compute = True
2384 if self._log_access:
2386 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2387 'create_date': 'TIMESTAMP',
2388 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2389 'write_date': 'TIMESTAMP'
2394 FROM pg_class c, pg_attribute a
2395 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2396 """, (self._table, k))
2398 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2400 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2401 self._table, k, logs[k])
2403 self._check_removed_columns(cr, log=False)
2405 # iterate on the "object columns"
2406 todo_update_store = []
2407 update_custom_fields = context.get('update_custom_fields', False)
2409 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 " \
2410 "FROM pg_class c,pg_attribute a,pg_type t " \
2411 "WHERE c.relname=%s " \
2412 "AND c.oid=a.attrelid " \
2413 "AND a.atttypid=t.oid", (self._table,))
2414 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2417 for k in self._columns:
2418 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2420 #Not Updating Custom fields
2421 if k.startswith('x_') and not update_custom_fields:
2424 f = self._columns[k]
2426 if isinstance(f, fields.one2many):
2427 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2429 if self.pool.get(f._obj):
2430 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2431 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2432 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2435 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))
2436 res = cr.fetchone()[0]
2438 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2439 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2440 self._obj, f._fields_id, f._table)
2441 elif isinstance(f, fields.many2many):
2442 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2443 if not cr.dictfetchall():
2444 if not self.pool.get(f._obj):
2445 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2446 ref = self.pool.get(f._obj)._table
2447 # ref = f._obj.replace('.', '_')
2448 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))
2449 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2450 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2451 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2453 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2454 f._rel, self._table, ref)
2456 res = col_data.get(k, [])
2457 res = res and [res] or []
2458 if not res and hasattr(f, 'oldname'):
2459 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 " \
2460 "FROM pg_class c,pg_attribute a,pg_type t " \
2461 "WHERE c.relname=%s " \
2462 "AND a.attname=%s " \
2463 "AND c.oid=a.attrelid " \
2464 "AND a.atttypid=t.oid", (self._table, f.oldname))
2465 res_old = cr.dictfetchall()
2466 if res_old and len(res_old) == 1:
2467 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2469 res[0]['attname'] = k
2470 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2471 self._table, f.oldname, k)
2475 f_pg_type = f_pg_def['typname']
2476 f_pg_size = f_pg_def['size']
2477 f_pg_notnull = f_pg_def['attnotnull']
2478 if isinstance(f, fields.function) and not f.store and\
2479 not getattr(f, 'nodrop', False):
2480 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2481 k, f.string, self._table)
2482 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2484 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2488 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2493 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2494 ('varchar', 'text', 'TEXT', ''),
2495 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2496 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2497 ('timestamp', 'date', 'date', '::date'),
2498 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2499 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2501 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2502 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2503 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2504 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2505 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2507 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2508 self._table, k, f_pg_size, f.size)
2510 if (f_pg_type==c[0]) and (f._type==c[1]):
2511 if f_pg_type != f_obj_type:
2513 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2514 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2515 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2516 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2518 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2519 self._table, k, c[0], c[1])
2522 if f_pg_type != f_obj_type:
2526 newname = k + '_moved' + str(i)
2527 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2528 "WHERE c.relname=%s " \
2529 "AND a.attname=%s " \
2530 "AND c.oid=a.attrelid ", (self._table, newname))
2531 if not cr.fetchone()[0]:
2535 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2536 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2537 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2538 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2539 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2540 self._table, k, f_pg_type, f._type, newname)
2542 # if the field is required and hasn't got a NOT NULL constraint
2543 if f.required and f_pg_notnull == 0:
2544 # set the field to the default value if any
2545 if k in self._defaults:
2546 if callable(self._defaults[k]):
2547 default = self._defaults[k](self, cr, 1, context)
2549 default = self._defaults[k]
2551 if (default is not None):
2552 ss = self._columns[k]._symbol_set
2553 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2554 cr.execute(query, (ss[1](default),))
2555 # add the NOT NULL constraint
2558 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2560 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2563 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2564 "If you want to have it, you should update the records and execute manually:\n"\
2565 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2566 self.__schema.warn(msg, self._table, k, self._table, k)
2568 elif not f.required and f_pg_notnull == 1:
2569 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2571 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2574 indexname = '%s_%s_index' % (self._table, k)
2575 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2576 res2 = cr.dictfetchall()
2577 if not res2 and f.select:
2578 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2580 if f._type == 'text':
2581 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2582 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2583 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2584 " because there is a length limit for indexable btree values!\n"\
2585 "Use a search view instead if you simply want to make the field searchable."
2586 self.__schema.warn(msg, self._table, k, f._type)
2587 if res2 and not f.select:
2588 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2590 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2591 self.__schema.warn(msg, self._table, k, f._type)
2593 if isinstance(f, fields.many2one):
2594 ref = self.pool.get(f._obj)._table
2595 if ref != 'ir_actions':
2596 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2597 'pg_attribute as att1, pg_attribute as att2 '
2598 'WHERE con.conrelid = cl1.oid '
2599 'AND cl1.relname = %s '
2600 'AND con.confrelid = cl2.oid '
2601 'AND cl2.relname = %s '
2602 'AND array_lower(con.conkey, 1) = 1 '
2603 'AND con.conkey[1] = att1.attnum '
2604 'AND att1.attrelid = cl1.oid '
2605 'AND att1.attname = %s '
2606 'AND array_lower(con.confkey, 1) = 1 '
2607 'AND con.confkey[1] = att2.attnum '
2608 'AND att2.attrelid = cl2.oid '
2609 'AND att2.attname = %s '
2610 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2611 res2 = cr.dictfetchall()
2613 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2614 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2615 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2617 self.__schema.debug("Table '%s': column '%s': XXX",
2620 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2622 if not isinstance(f, fields.function) or f.store:
2623 # add the missing field
2624 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2625 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2626 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2627 self._table, k, get_pg_type(f)[1])
2630 if not create and k in self._defaults:
2631 if callable(self._defaults[k]):
2632 default = self._defaults[k](self, cr, 1, context)
2634 default = self._defaults[k]
2636 ss = self._columns[k]._symbol_set
2637 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2638 cr.execute(query, (ss[1](default),))
2640 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2642 if isinstance(f, fields.function):
2644 if f.store is not True:
2645 order = f.store[f.store.keys()[0]][2]
2646 todo_update_store.append((order, f, k))
2648 # and add constraints if needed
2649 if isinstance(f, fields.many2one):
2650 if not self.pool.get(f._obj):
2651 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2652 ref = self.pool.get(f._obj)._table
2653 # ref = f._obj.replace('.', '_')
2654 # ir_actions is inherited so foreign key doesn't work on it
2655 if ref != 'ir_actions':
2656 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2657 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2658 self._table, k, ref, f.ondelete)
2660 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2664 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2665 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2668 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2669 "Try to re-run: openerp-server.py --update=module\n"\
2670 "If it doesn't work, update records and execute manually:\n"\
2671 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2672 self.__logger.warn(msg, k, self._table, self._table, k)
2674 for order, f, k in todo_update_store:
2675 todo_end.append((order, self._update_store, (f, k)))
2678 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2679 create = not bool(cr.fetchone())
2681 cr.commit() # start a new transaction
2683 for (key, con, _) in self._sql_constraints:
2684 conname = '%s_%s' % (self._table, key)
2686 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2687 existing_constraints = cr.dictfetchall()
2692 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2693 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2694 self._table, conname, con),
2695 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2700 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2701 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2702 '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" % (
2708 if not existing_constraints:
2709 # constraint does not exists:
2710 sql_actions['add']['execute'] = True
2711 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2712 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2713 # constraint exists but its definition has changed:
2714 sql_actions['drop']['execute'] = True
2715 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2716 sql_actions['add']['execute'] = True
2717 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2719 # we need to add the constraint:
2720 sql_actions = [item for item in sql_actions.values()]
2721 sql_actions.sort(key=lambda x: x['order'])
2722 for sql_action in [action for action in sql_actions if action['execute']]:
2724 cr.execute(sql_action['query'])
2726 self.__schema.debug(sql_action['msg_ok'])
2728 self.__schema.warn(sql_action['msg_err'])
2732 if hasattr(self, "_sql"):
2733 for line in self._sql.split(';'):
2734 line2 = line.replace('\n', '').strip()
2739 self._parent_store_compute(cr)
2743 def __init__(self, cr):
2744 super(orm, self).__init__(cr)
2746 if not hasattr(self, '_log_access'):
2747 # if not access is not specify, it is the same value as _auto
2748 self._log_access = getattr(self, "_auto", True)
2750 self._columns = self._columns.copy()
2751 for store_field in self._columns:
2752 f = self._columns[store_field]
2753 if hasattr(f, 'digits_change'):
2755 if not isinstance(f, fields.function):
2759 if self._columns[store_field].store is True:
2760 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2762 sm = self._columns[store_field].store
2763 for object, aa in sm.items():
2765 (fnct, fields2, order, length) = aa
2767 (fnct, fields2, order) = aa
2770 raise except_orm('Error',
2771 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2772 self.pool._store_function.setdefault(object, [])
2774 for x, y, z, e, f, l in self.pool._store_function[object]:
2775 if (x==self._name) and (y==store_field) and (e==fields2):
2779 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2780 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2782 for (key, _, msg) in self._sql_constraints:
2783 self.pool._sql_error[self._table+'_'+key] = msg
2785 # Load manual fields
2787 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2789 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2790 for field in cr.dictfetchall():
2791 if field['name'] in self._columns:
2794 'string': field['field_description'],
2795 'required': bool(field['required']),
2796 'readonly': bool(field['readonly']),
2797 'domain': field['domain'] or None,
2798 'size': field['size'],
2799 'ondelete': field['on_delete'],
2800 'translate': (field['translate']),
2801 #'select': int(field['select_level'])
2804 if field['ttype'] == 'selection':
2805 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2806 elif field['ttype'] == 'reference':
2807 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2808 elif field['ttype'] == 'many2one':
2809 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2810 elif field['ttype'] == 'one2many':
2811 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2812 elif field['ttype'] == 'many2many':
2813 _rel1 = field['relation'].replace('.', '_')
2814 _rel2 = field['model'].replace('.', '_')
2815 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2816 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2818 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2819 self._inherits_check()
2820 self._inherits_reload()
2821 if not self._sequence:
2822 self._sequence = self._table + '_id_seq'
2823 for k in self._defaults:
2824 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,)
2825 for f in self._columns:
2826 self._columns[f].restart()
2829 # Update objects that uses this one to update their _inherits fields
2832 def _inherits_reload_src(self):
2833 for obj in self.pool.obj_pool.values():
2834 if self._name in obj._inherits:
2835 obj._inherits_reload()
2837 def _inherits_reload(self):
2839 for table in self._inherits:
2840 res.update(self.pool.get(table)._inherit_fields)
2841 for col in self.pool.get(table)._columns.keys():
2842 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2843 for col in self.pool.get(table)._inherit_fields.keys():
2844 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2845 self._inherit_fields = res
2846 self._inherits_reload_src()
2848 def _inherits_check(self):
2849 for table, field_name in self._inherits.items():
2850 if field_name not in self._columns:
2851 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2852 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2853 required=True, ondelete="cascade")
2854 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2855 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))
2856 self._columns[field_name].required = True
2857 self._columns[field_name].ondelete = "cascade"
2859 #def __getattr__(self, name):
2861 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2862 # (though inherits doesn't use Python inheritance).
2863 # Handles translating between local ids and remote ids.
2864 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2865 # when you have inherits.
2867 # for model, field in self._inherits.iteritems():
2868 # proxy = self.pool.get(model)
2869 # if hasattr(proxy, name):
2870 # attribute = getattr(proxy, name)
2871 # if not hasattr(attribute, '__call__'):
2875 # return super(orm, self).__getattr__(name)
2877 # def _proxy(cr, uid, ids, *args, **kwargs):
2878 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2879 # lst = [obj[field].id for obj in objects if obj[field]]
2880 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2885 def fields_get(self, cr, user, fields=None, context=None):
2887 Get the description of list of fields
2889 :param cr: database cursor
2890 :param user: current user id
2891 :param fields: list of fields
2892 :param context: context arguments, like lang, time zone
2893 :return: dictionary of field dictionaries, each one describing a field of the business object
2894 :raise AccessError: * if user has no create/write rights on the requested object
2897 ira = self.pool.get('ir.model.access')
2898 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2899 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2900 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2902 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2905 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2907 fields = self._columns.keys() + self._inherit_fields.keys()
2908 if isinstance(ids, (int, long)):
2912 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
2913 result = self._read_flat(cr, user, select, fields, context, load)
2916 for key, v in r.items():
2920 if isinstance(ids, (int, long, dict)):
2921 return result and result[0] or False
2924 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2929 if fields_to_read == None:
2930 fields_to_read = self._columns.keys()
2932 # Construct a clause for the security rules.
2933 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2934 # or will at least contain self._table.
2935 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2937 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2938 fields_pre = [f for f in fields_to_read if
2939 f == self.CONCURRENCY_CHECK_FIELD
2940 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2941 ] + self._inherits.values()
2945 def convert_field(f):
2946 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2947 if f in ('create_date', 'write_date'):
2948 return "date_trunc('second', %s) as %s" % (f_qual, f)
2949 if f == self.CONCURRENCY_CHECK_FIELD:
2950 if self._log_access:
2951 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2952 return "now()::timestamp AS %s" % (f,)
2953 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2954 return 'length(%s) as "%s"' % (f_qual, f)
2957 fields_pre2 = map(convert_field, fields_pre)
2958 order_by = self._parent_order or self._order
2959 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2960 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2962 query += " AND " + (' OR '.join(rule_clause))
2963 query += " ORDER BY " + order_by
2964 for sub_ids in cr.split_for_in_conditions(ids):
2966 cr.execute(query, [tuple(sub_ids)] + rule_params)
2967 if cr.rowcount != len(sub_ids):
2968 raise except_orm(_('AccessError'),
2969 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
2970 % (self._description,))
2972 cr.execute(query, (tuple(sub_ids),))
2973 res.extend(cr.dictfetchall())
2975 res = map(lambda x: {'id': x}, ids)
2977 for f in fields_pre:
2978 if f == self.CONCURRENCY_CHECK_FIELD:
2980 if self._columns[f].translate:
2981 ids = [x['id'] for x in res]
2982 #TODO: optimize out of this loop
2983 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2985 r[f] = res_trans.get(r['id'], False) or r[f]
2987 for table in self._inherits:
2988 col = self._inherits[table]
2989 cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
2992 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3000 if not record[col]: # if the record is deleted from _inherits table?
3002 record.update(res3[record[col]])
3003 if col not in fields_to_read:
3006 # all fields which need to be post-processed by a simple function (symbol_get)
3007 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3010 for f in fields_post:
3011 r[f] = self._columns[f]._symbol_get(r[f])
3012 ids = [x['id'] for x in res]
3014 # all non inherited fields for which the attribute whose name is in load is False
3015 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3017 # Compute POST fields
3019 for f in fields_post:
3020 todo.setdefault(self._columns[f]._multi, [])
3021 todo[self._columns[f]._multi].append(f)
3022 for key, val in todo.items():
3024 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3027 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3028 multi_fields = res2.get(record['id'],{})
3030 record[pos] = multi_fields.get(pos,[])
3033 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3036 record[f] = res2[record['id']]
3041 for field in vals.copy():
3043 if field in self._columns:
3044 fobj = self._columns[field]
3051 for group in groups:
3052 module = group.split(".")[0]
3053 grp = group.split(".")[1]
3054 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" \
3055 (grp, module, 'res.groups', user))
3056 readonly = cr.fetchall()
3057 if readonly[0][0] >= 1:
3060 elif readonly[0][0] == 0:
3066 if type(vals[field]) == type([]):
3068 elif type(vals[field]) == type(0.0):
3070 elif type(vals[field]) == type(''):
3071 vals[field] = '=No Permission='
3076 def perm_read(self, cr, user, ids, context=None, details=True):
3078 Returns some metadata about the given records.
3080 :param details: if True, \*_uid fields are replaced with the name of the user
3081 :return: list of ownership dictionaries for each requested record
3082 :rtype: list of dictionaries with the following keys:
3085 * create_uid: user who created the record
3086 * create_date: date when the record was created
3087 * write_uid: last user who changed the record
3088 * write_date: date of the last change to the record
3089 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3096 uniq = isinstance(ids, (int, long))
3100 if self._log_access:
3101 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3102 quoted_table = '"%s"' % self._table
3103 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3104 query = '''SELECT %s, __imd.module, __imd.name
3105 FROM %s LEFT JOIN ir_model_data __imd
3106 ON (__imd.model = %%s and __imd.res_id = %s.id)
3107 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3108 cr.execute(query, (self._name, tuple(ids)))
3109 res = cr.dictfetchall()
3112 r[key] = r[key] or False
3113 if details and key in ('write_uid', 'create_uid') and r[key]:
3115 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3117 pass # Leave the numeric uid there
3118 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3119 del r['name'], r['module']
3124 def _check_concurrency(self, cr, ids, context):
3127 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3129 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3130 for sub_ids in cr.split_for_in_conditions(ids):
3133 id_ref = "%s,%s" % (self._name, id)
3134 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3136 ids_to_check.extend([id, update_date])
3137 if not ids_to_check:
3139 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3142 # mention the first one only to keep the error message readable
3143 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3145 def check_access_rule(self, cr, uid, ids, operation, context=None):
3146 """Verifies that the operation given by ``operation`` is allowed for the user
3147 according to ir.rules.
3149 :param operation: one of ``write``, ``unlink``
3150 :raise except_orm: * if current ir.rules do not permit this operation.
3151 :return: None if the operation is allowed
3153 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3155 where_clause = ' and ' + ' and '.join(where_clause)
3156 for sub_ids in cr.split_for_in_conditions(ids):
3157 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3158 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3159 [sub_ids] + where_params)
3160 if cr.rowcount != len(sub_ids):
3161 raise except_orm(_('AccessError'),
3162 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3163 % (operation, self._description))
3165 def unlink(self, cr, uid, ids, context=None):
3167 Delete records with given ids
3169 :param cr: database cursor
3170 :param uid: current user id
3171 :param ids: id or list of ids
3172 :param context: (optional) context arguments, like lang, time zone
3174 :raise AccessError: * if user has no unlink rights on the requested object
3175 * if user tries to bypass access rules for unlink on the requested object
3176 :raise UserError: if the record is default property for other records
3181 if isinstance(ids, (int, long)):
3184 result_store = self._store_get_values(cr, uid, ids, None, context)
3186 self._check_concurrency(cr, ids, context)
3188 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3190 properties = self.pool.get('ir.property')
3191 domain = [('res_id', '=', False),
3192 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3194 if properties.search(cr, uid, domain, context=context):
3195 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3197 wf_service = netsvc.LocalService("workflow")
3199 wf_service.trg_delete(uid, self._name, oid, cr)
3202 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3203 for sub_ids in cr.split_for_in_conditions(ids):
3204 cr.execute('delete from ' + self._table + ' ' \
3205 'where id IN %s', (sub_ids,))
3206 for order, object, store_ids, fields in result_store:
3207 if object != self._name:
3208 obj = self.pool.get(object)
3209 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3210 rids = map(lambda x: x[0], cr.fetchall())
3212 obj._store_set_values(cr, uid, rids, fields, context)
3218 def write(self, cr, user, ids, vals, context=None):
3220 Update records with given ids with the given field values
3222 :param cr: database cursor
3223 :param user: current user id
3225 :param ids: object id or list of object ids to update according to **vals**
3226 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3227 :type vals: dictionary
3228 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3229 :type context: dictionary
3231 :raise AccessError: * if user has no write rights on the requested object
3232 * if user tries to bypass access rules for write on the requested object
3233 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3234 :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)
3236 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3238 + For a many2many field, a list of tuples is expected.
3239 Here is the list of tuple that are accepted, with the corresponding semantics ::
3241 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3242 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3243 (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)
3244 (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)
3245 (4, ID) link to existing record with id = ID (adds a relationship)
3246 (5) unlink all (like using (3,ID) for all linked records)
3247 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3250 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3252 + For a one2many field, a lits of tuples is expected.
3253 Here is the list of tuple that are accepted, with the corresponding semantics ::
3255 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3256 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3257 (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)
3260 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3262 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3263 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3267 for field in vals.copy():
3269 if field in self._columns:
3270 fobj = self._columns[field]
3271 elif field in self._inherit_fields:
3272 fobj = self._inherit_fields[field][2]
3279 for group in groups:
3280 module = group.split(".")[0]
3281 grp = group.split(".")[1]
3282 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", \
3283 (grp, module, 'res.groups', user))
3284 readonly = cr.fetchall()
3285 if readonly[0][0] >= 1:
3288 elif readonly[0][0] == 0:
3300 if isinstance(ids, (int, long)):
3303 self._check_concurrency(cr, ids, context)
3304 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3306 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3308 # No direct update of parent_left/right
3309 vals.pop('parent_left', None)
3310 vals.pop('parent_right', None)
3312 parents_changed = []
3313 if self._parent_store and (self._parent_name in vals):
3314 # The parent_left/right computation may take up to
3315 # 5 seconds. No need to recompute the values if the
3316 # parent is the same. Get the current value of the parent
3317 parent_val = vals[self._parent_name]
3319 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3320 (self._table, self._parent_name, self._parent_name)
3321 cr.execute(query, (tuple(ids), parent_val))
3323 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3324 (self._table, self._parent_name)
3325 cr.execute(query, (tuple(ids),))
3326 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3333 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3335 if field in self._columns:
3336 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3337 if (not totranslate) or not self._columns[field].translate:
3338 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3339 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3340 direct.append(field)
3342 upd_todo.append(field)
3344 updend.append(field)
3345 if field in self._columns \
3346 and hasattr(self._columns[field], 'selection') \
3348 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3350 if self._log_access:
3351 upd0.append('write_uid=%s')
3352 upd0.append('write_date=now()')
3356 self.check_access_rule(cr, user, ids, 'write', context=context)
3357 for sub_ids in cr.split_for_in_conditions(ids):
3358 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3359 'where id IN %s', upd1 + [sub_ids])
3360 if cr.rowcount != len(sub_ids):
3361 raise except_orm(_('AccessError'),
3362 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3367 if self._columns[f].translate:
3368 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3371 # Inserting value to DB
3372 self.write(cr, user, ids, {f: vals[f]})
3373 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3376 # call the 'set' method of fields which are not classic_write
3377 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3379 # default element in context must be removed when call a one2many or many2many
3380 rel_context = context.copy()
3381 for c in context.items():
3382 if c[0].startswith('default_'):
3383 del rel_context[c[0]]
3385 for field in upd_todo:
3387 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3389 for table in self._inherits:
3390 col = self._inherits[table]
3392 for sub_ids in cr.split_for_in_conditions(ids):
3393 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3394 'where id IN %s', (sub_ids,))
3395 nids.extend([x[0] for x in cr.fetchall()])
3399 if self._inherit_fields[val][0] == table:
3402 self.pool.get(table).write(cr, user, nids, v, context)
3404 self._validate(cr, user, ids, context)
3406 # TODO: use _order to set dest at the right position and not first node of parent
3407 # We can't defer parent_store computation because the stored function
3408 # fields that are computer may refer (directly or indirectly) to
3409 # parent_left/right (via a child_of domain)
3412 self.pool._init_parent[self._name] = True
3414 order = self._parent_order or self._order
3415 parent_val = vals[self._parent_name]
3417 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3419 clause, params = '%s IS NULL' % (self._parent_name,), ()
3420 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3421 parents = cr.fetchall()
3423 for id in parents_changed:
3424 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3425 pleft, pright = cr.fetchone()
3426 distance = pright - pleft + 1
3428 # Find Position of the element
3430 for (parent_pright, parent_id) in parents:
3433 position = parent_pright + 1
3435 # It's the first node of the parent
3440 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3441 position = cr.fetchone()[0] + 1
3443 if pleft < position <= pright:
3444 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3446 if pleft < position:
3447 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3448 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3449 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))
3451 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3452 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3453 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))
3455 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3459 for order, object, ids, fields in result:
3460 key = (object, tuple(fields))
3461 done.setdefault(key, {})
3462 # avoid to do several times the same computation
3465 if id not in done[key]:
3466 done[key][id] = True
3468 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3470 wf_service = netsvc.LocalService("workflow")
3472 wf_service.trg_write(user, self._name, id, cr)
3476 # TODO: Should set perm to user.xxx
3478 def create(self, cr, user, vals, context=None):
3480 Create new record with specified value
3482 :param cr: database cursor
3483 :param user: current user id
3485 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3486 :type vals: dictionary
3487 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3488 :type context: dictionary
3489 :return: id of new record created
3490 :raise AccessError: * if user has no create rights on the requested object
3491 * if user tries to bypass access rules for create on the requested object
3492 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3493 :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)
3495 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3496 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3502 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3504 vals = self._add_missing_default_values(cr, user, vals, context)
3507 for v in self._inherits:
3508 if self._inherits[v] not in vals:
3511 tocreate[v] = {'id': vals[self._inherits[v]]}
3512 (upd0, upd1, upd2) = ('', '', [])
3514 for v in vals.keys():
3515 if v in self._inherit_fields:
3516 (table, col, col_detail) = self._inherit_fields[v]
3517 tocreate[table][v] = vals[v]
3520 if (v not in self._inherit_fields) and (v not in self._columns):
3523 # Try-except added to filter the creation of those records whose filds are readonly.
3524 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3526 cr.execute("SELECT nextval('"+self._sequence+"')")
3528 raise except_orm(_('UserError'),
3529 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3531 id_new = cr.fetchone()[0]
3532 for table in tocreate:
3533 if self._inherits[table] in vals:
3534 del vals[self._inherits[table]]
3536 record_id = tocreate[table].pop('id', None)
3538 if record_id is None or not record_id:
3539 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3541 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3543 upd0 += ',' + self._inherits[table]
3545 upd2.append(record_id)
3547 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3548 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3550 for bool_field in bool_fields:
3551 if bool_field not in vals:
3552 vals[bool_field] = False
3554 for field in vals.copy():
3556 if field in self._columns:
3557 fobj = self._columns[field]
3559 fobj = self._inherit_fields[field][2]
3565 for group in groups:
3566 module = group.split(".")[0]
3567 grp = group.split(".")[1]
3568 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" % \
3569 (grp, module, 'res.groups', user))
3570 readonly = cr.fetchall()
3571 if readonly[0][0] >= 1:
3574 elif readonly[0][0] == 0:
3582 if self._columns[field]._classic_write:
3583 upd0 = upd0 + ',"' + field + '"'
3584 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3585 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3587 if not isinstance(self._columns[field], fields.related):
3588 upd_todo.append(field)
3589 if field in self._columns \
3590 and hasattr(self._columns[field], 'selection') \
3592 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3593 if self._log_access:
3594 upd0 += ',create_uid,create_date'
3597 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3598 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3599 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3601 if self._parent_store and not context.get('defer_parent_store_computation'):
3603 self.pool._init_parent[self._name] = True
3605 parent = vals.get(self._parent_name, False)
3607 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3609 result_p = cr.fetchall()
3610 for (pleft,) in result_p:
3615 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3616 pleft_old = cr.fetchone()[0]
3619 cr.execute('select max(parent_right) from '+self._table)
3620 pleft = cr.fetchone()[0] or 0
3621 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3622 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3623 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3625 # default element in context must be remove when call a one2many or many2many
3626 rel_context = context.copy()
3627 for c in context.items():
3628 if c[0].startswith('default_'):
3629 del rel_context[c[0]]
3632 for field in upd_todo:
3633 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3634 self._validate(cr, user, [id_new], context)
3636 if not context.get('no_store_function', False):
3637 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3640 for order, object, ids, fields2 in result:
3641 if not (object, ids, fields2) in done:
3642 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3643 done.append((object, ids, fields2))
3645 if self._log_create and not (context and context.get('no_store_function', False)):
3646 message = self._description + \
3648 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3649 "' " + _("created.")
3650 self.log(cr, user, id_new, message, True, context=context)
3651 wf_service = netsvc.LocalService("workflow")
3652 wf_service.trg_create(user, self._name, id_new, cr)
3655 def _store_get_values(self, cr, uid, ids, fields, context):
3657 fncts = self.pool._store_function.get(self._name, [])
3658 for fnct in range(len(fncts)):
3663 for f in (fields or []):
3664 if f in fncts[fnct][3]:
3670 result.setdefault(fncts[fnct][0], {})
3672 # uid == 1 for accessing objects having rules defined on store fields
3673 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3674 for id in filter(None, ids2):
3675 result[fncts[fnct][0]].setdefault(id, [])
3676 result[fncts[fnct][0]][id].append(fnct)
3678 for object in result:
3680 for id, fnct in result[object].items():
3681 k2.setdefault(tuple(fnct), [])
3682 k2[tuple(fnct)].append(id)
3683 for fnct, id in k2.items():
3684 dict.setdefault(fncts[fnct[0]][4], [])
3685 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3693 def _store_set_values(self, cr, uid, ids, fields, context):
3698 if self._log_access:
3699 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3703 field_dict.setdefault(r[0], [])
3704 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3705 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3706 for i in self.pool._store_function.get(self._name, []):
3708 up_write_date = write_date + datetime.timedelta(hours=i[5])
3709 if datetime.datetime.now() < up_write_date:
3711 field_dict[r[0]].append(i[1])
3717 if self._columns[f]._multi not in keys:
3718 keys.append(self._columns[f]._multi)
3719 todo.setdefault(self._columns[f]._multi, [])
3720 todo[self._columns[f]._multi].append(f)
3724 # uid == 1 for accessing objects having rules defined on store fields
3725 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3726 for id, value in result.items():
3728 for f in value.keys():
3729 if f in field_dict[id]:
3736 if self._columns[v]._type in ('many2one', 'one2one'):
3738 value[v] = value[v][0]
3741 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3742 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3745 cr.execute('update "' + self._table + '" set ' + \
3746 ','.join(upd0) + ' where id = %s', upd1)
3750 # uid == 1 for accessing objects having rules defined on store fields
3751 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3752 for r in result.keys():
3754 if r in field_dict.keys():
3755 if f in field_dict[r]:
3757 for id, value in result.items():
3758 if self._columns[f]._type in ('many2one', 'one2one'):
3763 cr.execute('update "' + self._table + '" set ' + \
3764 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3770 def perm_write(self, cr, user, ids, fields, context=None):
3771 raise NotImplementedError(_('This method does not exist anymore'))
3773 # TODO: ameliorer avec NULL
3774 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3775 """Computes the WHERE clause needed to implement an OpenERP domain.
3776 :param domain: the domain to compute
3778 :param active_test: whether the default filtering of records with ``active``
3779 field set to ``False`` should be applied.
3780 :return: the query expressing the given domain as provided in domain
3781 :rtype: osv.query.Query
3786 # if the object has a field named 'active', filter out all inactive
3787 # records unless they were explicitely asked for
3788 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3790 active_in_args = False
3792 if a[0] == 'active':
3793 active_in_args = True
3794 if not active_in_args:
3795 domain.insert(0, ('active', '=', 1))
3797 domain = [('active', '=', 1)]
3801 e = expression.expression(domain)
3802 e.parse(cr, user, self, context)
3803 tables = e.get_tables()
3804 where_clause, where_params = e.to_sql()
3805 where_clause = where_clause and [where_clause] or []
3807 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3809 return Query(tables, where_clause, where_params)
3811 def _check_qorder(self, word):
3812 if not regex_order.match(word):
3813 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)'))
3816 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3817 """Add what's missing in ``query`` to implement all appropriate ir.rules
3818 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3820 :param query: the current query object
3822 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3824 if parent_model and child_object:
3825 # as inherited rules are being applied, we need to add the missing JOIN
3826 # to reach the parent table (if it was not JOINed yet in the query)
3827 child_object._inherits_join_add(parent_model, query)
3828 query.where_clause += added_clause
3829 query.where_clause_params += added_params
3830 for table in added_tables:
3831 if table not in query.tables:
3832 query.tables.append(table)
3836 # apply main rules on the object
3837 rule_obj = self.pool.get('ir.rule')
3838 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3840 # apply ir.rules from the parents (through _inherits)
3841 for inherited_model in self._inherits:
3842 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3843 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3845 def _generate_m2o_order_by(self, order_field, query):
3847 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3848 either native m2o fields or function/related fields that are stored, including
3849 intermediate JOINs for inheritance if required.
3851 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3853 if order_field not in self._columns and order_field in self._inherit_fields:
3854 # also add missing joins for reaching the table containing the m2o field
3855 qualified_field = self._inherits_join_calc(order_field, query)
3856 order_field_column = self._inherit_fields[order_field][2]
3858 qualified_field = '"%s"."%s"' % (self._table, order_field)
3859 order_field_column = self._columns[order_field]
3861 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3862 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
3863 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
3864 "to be used as ordering fields! Ignoring sorting for %s.%s",
3865 self._name, order_field)
3868 # figure out the applicable order_by for the m2o
3869 dest_model = self.pool.get(order_field_column._obj)
3870 m2o_order = dest_model._order
3871 if not regex_order.match(m2o_order):
3872 # _order is complex, can't use it here, so we default to _rec_name
3873 m2o_order = dest_model._rec_name
3875 # extract the field names, to be able to qualify them and add desc/asc
3877 for order_part in m2o_order.split(",",1):
3878 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
3879 m2o_order = m2o_order_list
3881 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3882 # as we don't want to exclude results that have NULL values for the m2o
3883 src_table, src_field = qualified_field.replace('"','').split('.', 1)
3884 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3885 qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
3886 return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
3889 def _generate_order_by(self, order_spec, query):
3891 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
3892 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
3894 :raise" except_orm in case order_spec is malformed
3896 order_by_clause = self._order
3898 order_by_elements = []
3899 self._check_qorder(order_spec)
3900 for order_part in order_spec.split(','):
3901 order_split = order_part.strip().split(' ')
3902 order_field = order_split[0].strip()
3903 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
3905 if order_field in self._columns:
3906 order_column = self._columns[order_field]
3907 if order_column._classic_read:
3908 inner_clause = '"%s"."%s"' % (self._table, order_field)
3909 elif order_column._type == 'many2one':
3910 inner_clause = self._generate_m2o_order_by(order_field, query)
3912 continue # ignore non-readable or "non-joinable" fields
3913 elif order_field in self._inherit_fields:
3914 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
3915 order_column = parent_obj._columns[order_field]
3916 if order_column._classic_read:
3917 inner_clause = self._inherits_join_calc(order_field, query)
3918 elif order_column._type == 'many2one':
3919 inner_clause = self._generate_m2o_order_by(order_field, query)
3921 continue # ignore non-readable or "non-joinable" fields
3923 if isinstance(inner_clause, list):
3924 for clause in inner_clause:
3925 order_by_elements.append("%s %s" % (clause, order_direction))
3927 order_by_elements.append("%s %s" % (inner_clause, order_direction))
3928 if order_by_elements:
3929 order_by_clause = ",".join(order_by_elements)
3931 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
3933 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
3935 Private implementation of search() method, allowing specifying the uid to use for the access right check.
3936 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
3937 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
3938 This is ok at the security level because this method is private and not callable through XML-RPC.
3940 :param access_rights_uid: optional user ID to use when checking access rights
3941 (not for ir.rules, this is only for ir.model.access)
3945 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
3947 query = self._where_calc(cr, user, args, context=context)
3948 self._apply_ir_rules(cr, user, query, 'read', context=context)
3949 order_by = self._generate_order_by(order, query)
3950 from_clause, where_clause, where_clause_params = query.get_sql()
3952 limit_str = limit and ' limit %d' % limit or ''
3953 offset_str = offset and ' offset %d' % offset or ''
3954 where_str = where_clause and (" WHERE %s" % where_clause) or ''
3957 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
3960 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
3962 return [x[0] for x in res]
3964 # returns the different values ever entered for one field
3965 # this is used, for example, in the client when the user hits enter on
3967 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3970 if field in self._inherit_fields:
3971 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3973 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3975 def copy_data(self, cr, uid, id, default=None, context=None):
3977 Copy given record's data with all its fields values
3979 :param cr: database cursor
3980 :param user: current user id
3981 :param id: id of the record to copy
3982 :param default: field values to override in the original values of the copied record
3983 :type default: dictionary
3984 :param context: context arguments, like lang, time zone
3985 :type context: dictionary
3986 :return: dictionary containing all the field values
3992 # avoid recursion through already copied records in case of circular relationship
3993 seen_map = context.setdefault('__copy_data_seen',{})
3994 if id in seen_map.setdefault(self._name,[]):
3996 seen_map[self._name].append(id)
4000 if 'state' not in default:
4001 if 'state' in self._defaults:
4002 if callable(self._defaults['state']):
4003 default['state'] = self._defaults['state'](self, cr, uid, context)
4005 default['state'] = self._defaults['state']
4007 context_wo_lang = context.copy()
4008 if 'lang' in context:
4009 del context_wo_lang['lang']
4010 data = self.read(cr, uid, [id,], context=context_wo_lang)
4014 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4016 fields = self.fields_get(cr, uid, context=context)
4018 ftype = fields[f]['type']
4020 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4024 data[f] = default[f]
4025 elif 'function' in fields[f]:
4027 elif ftype == 'many2one':
4029 data[f] = data[f] and data[f][0]
4032 elif ftype in ('one2many', 'one2one'):
4034 rel = self.pool.get(fields[f]['relation'])
4036 # duplicate following the order of the ids
4037 # because we'll rely on it later for copying
4038 # translations in copy_translation()!
4040 for rel_id in data[f]:
4041 # the lines are first duplicated using the wrong (old)
4042 # parent but then are reassigned to the correct one thanks
4043 # to the (0, 0, ...)
4044 d = rel.copy_data(cr, uid, rel_id, context=context)
4046 res.append((0, 0, d))
4048 elif ftype == 'many2many':
4049 data[f] = [(6, 0, data[f])]
4053 # make sure we don't break the current parent_store structure and
4054 # force a clean recompute!
4055 for parent_column in ['parent_left', 'parent_right']:
4056 data.pop(parent_column, None)
4058 for v in self._inherits:
4059 del data[self._inherits[v]]
4062 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4066 # avoid recursion through already copied records in case of circular relationship
4067 seen_map = context.setdefault('__copy_translations_seen',{})
4068 if old_id in seen_map.setdefault(self._name,[]):
4070 seen_map[self._name].append(old_id)
4072 trans_obj = self.pool.get('ir.translation')
4073 fields = self.fields_get(cr, uid, context=context)
4075 translation_records = []
4076 for field_name, field_def in fields.items():
4077 # we must recursively copy the translations for o2o and o2m
4078 if field_def['type'] in ('one2one', 'one2many'):
4079 target_obj = self.pool.get(field_def['relation'])
4080 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4081 # here we rely on the order of the ids to match the translations
4082 # as foreseen in copy_data()
4083 old_children = sorted(old_record[field_name])
4084 new_children = sorted(new_record[field_name])
4085 for (old_child, new_child) in zip(old_children, new_children):
4086 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4087 # and for translatable fields we keep them for copy
4088 elif field_def.get('translate'):
4090 if field_name in self._columns:
4091 trans_name = self._name + "," + field_name
4092 elif field_name in self._inherit_fields:
4093 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4095 trans_ids = trans_obj.search(cr, uid, [
4096 ('name', '=', trans_name),
4097 ('res_id', '=', old_id)
4099 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4101 for record in translation_records:
4103 record['res_id'] = new_id
4104 trans_obj.create(cr, uid, record, context=context)
4107 def copy(self, cr, uid, id, default=None, context=None):
4109 Duplicate record with given id updating it with default values
4111 :param cr: database cursor
4112 :param uid: current user id
4113 :param id: id of the record to copy
4114 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4115 :type default: dictionary
4116 :param context: context arguments, like lang, time zone
4117 :type context: dictionary
4123 context = context.copy()
4124 data = self.copy_data(cr, uid, id, default, context)
4125 new_id = self.create(cr, uid, data, context)
4126 self.copy_translations(cr, uid, id, new_id, context)
4129 def exists(self, cr, uid, ids, context=None):
4130 if type(ids) in (int, long):
4132 query = 'SELECT count(1) FROM "%s"' % (self._table)
4133 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4134 return cr.fetchone()[0] == len(ids)
4136 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4137 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4138 self._name, DeprecationWarning, stacklevel=3)
4139 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4140 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4141 return self._check_recursion(cr, uid, ids, context, parent)
4143 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4145 Verifies that there is no loop in a hierarchical structure of records,
4146 by following the parent relationship using the **parent** field until a loop
4147 is detected or until a top-level record is found.
4149 :param cr: database cursor
4150 :param uid: current user id
4151 :param ids: list of ids of records to check
4152 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4153 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4157 parent = self._parent_name
4159 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4162 for i in range(0, len(ids), cr.IN_MAX):
4163 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4164 cr.execute(query, (tuple(sub_ids_parent),))
4165 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4166 ids_parent = ids_parent2
4167 for i in ids_parent:
4172 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4173 """Find out the XML ID of any database record, if there
4174 is one. This method works as a possible implementation
4175 for a function field, to be able to add it to any
4176 model object easily, referencing it as ``osv.osv.get_xml_id``.
4178 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4180 :return: the fully qualified XML ID of the given object,
4181 defaulting to an empty string when there's none
4182 (to be usable as a function field).
4184 result = dict.fromkeys(ids, '')
4185 model_data_obj = self.pool.get('ir.model.data')
4186 data_ids = model_data_obj.search(cr, uid,
4187 [('model', '=', self._name), ('res_id', 'in', ids)])
4188 data_results = model_data_obj.read(cr, uid, data_ids,
4189 ['name', 'module', 'res_id'])
4190 for record in data_results:
4191 result[record['res_id']] = '%(module)s.%(name)s' % record
4194 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: