1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 # Object relationnal mapping to postgresql module
24 # . Hierarchical structure
25 # . Constraints consistency, validations
26 # . Object meta Data depends on its status
27 # . Optimised processing by complex query (multiple actions at once)
28 # . Default fields value
29 # . Permissions optimisation
30 # . Persistant object: DB postgresql
32 # . Multi-level caching system
33 # . 2 different inheritancies
35 # - classicals (varchar, integer, boolean, ...)
36 # - relations (one2many, many2one, many2many)
53 from lxml import etree
54 from tools.config import config
55 from tools.translate import _
58 from query import Query
60 from tools.safe_eval import safe_eval as eval
62 # List of etree._Element subclasses that we choose to ignore when parsing XML.
63 from tools import SKIPPED_ELEMENT_TYPES
65 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
67 POSTGRES_CONFDELTYPES = {
75 def last_day_of_current_month():
76 today = datetime.date.today()
77 last_day = str(calendar.monthrange(today.year, today.month)[1])
78 return time.strftime('%Y-%m-' + last_day)
80 def intersect(la, lb):
81 return filter(lambda x: x in lb, la)
83 class except_orm(Exception):
84 def __init__(self, name, value):
87 self.args = (name, value)
89 class BrowseRecordError(Exception):
92 # Readonly python database object browser
93 class browse_null(object):
98 def __getitem__(self, name):
101 def __getattr__(self, name):
102 return None # XXX: return self ?
110 def __nonzero__(self):
113 def __unicode__(self):
118 # TODO: execute an object method on browse_record_list
120 class browse_record_list(list):
122 def __init__(self, lst, context=None):
125 super(browse_record_list, self).__init__(lst)
126 self.context = context
129 class browse_record(object):
130 logger = netsvc.Logger()
132 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
134 table : the object (inherited from orm)
135 context : dictionary with an optional context
137 if fields_process is None:
141 self._list_class = list_class or browse_record_list
146 self._table_name = self._table._name
147 self.__logger = logging.getLogger(
148 'osv.browse_record.' + self._table_name)
149 self._context = context
150 self._fields_process = fields_process
152 cache.setdefault(table._name, {})
153 self._data = cache[table._name]
155 if not (id and isinstance(id, (int, long,))):
156 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
157 # if not table.exists(cr, uid, id, context):
158 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
160 if id not in self._data:
161 self._data[id] = {'id': id}
165 def __getitem__(self, name):
169 if name not in self._data[self._id]:
170 # build the list of fields we will fetch
172 # fetch the definition of the field which was asked for
173 if name in self._table._columns:
174 col = self._table._columns[name]
175 elif name in self._table._inherit_fields:
176 col = self._table._inherit_fields[name][2]
177 elif hasattr(self._table, str(name)):
178 attr = getattr(self._table, name)
180 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
181 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
185 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
186 "Field '%s' does not exist in object '%s': \n%s" % (
187 name, self, ''.join(traceback.format_exc())))
188 raise KeyError("Field '%s' does not exist in object '%s'" % (
191 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
193 # gen the list of "local" (ie not inherited) fields which are classic or many2one
194 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
195 # gen the list of inherited fields
196 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
197 # complete the field list with the inherited fields which are classic or many2one
198 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
199 # otherwise we fetch only that field
201 fields_to_fetch = [(name, col)]
202 ids = filter(lambda id: name not in self._data[id], self._data.keys())
204 field_names = map(lambda x: x[0], fields_to_fetch)
205 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
206 if self._fields_process:
207 lang = self._context.get('lang', 'en_US') or 'en_US'
208 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
210 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
211 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
213 for field_name, field_column in fields_to_fetch:
214 if field_column._type in self._fields_process:
215 for result_line in field_values:
216 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
217 if result_line[field_name]:
218 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
221 # Where did those ids come from? Perhaps old entries in ir_model_dat?
222 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
223 raise KeyError('Field %s not found in %s'%(name, self))
224 # create browse records for 'remote' objects
225 for result_line in field_values:
227 for field_name, field_column in fields_to_fetch:
228 if field_column._type in ('many2one', 'one2one'):
229 if result_line[field_name]:
230 obj = self._table.pool.get(field_column._obj)
231 if isinstance(result_line[field_name], (list, tuple)):
232 value = result_line[field_name][0]
234 value = result_line[field_name]
236 # FIXME: this happen when a _inherits object
237 # overwrite a field of it parent. Need
238 # testing to be sure we got the right
239 # object and not the parent one.
240 if not isinstance(value, browse_record):
241 new_data[field_name] = browse_record(self._cr,
242 self._uid, value, obj, self._cache,
243 context=self._context,
244 list_class=self._list_class,
245 fields_process=self._fields_process)
247 new_data[field_name] = value
249 new_data[field_name] = browse_null()
251 new_data[field_name] = browse_null()
252 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
253 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)
254 elif field_column._type in ('reference'):
255 if result_line[field_name]:
256 if isinstance(result_line[field_name], browse_record):
257 new_data[field_name] = result_line[field_name]
259 ref_obj, ref_id = result_line[field_name].split(',')
260 ref_id = long(ref_id)
262 obj = self._table.pool.get(ref_obj)
263 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)
265 new_data[field_name] = browse_null()
267 new_data[field_name] = browse_null()
269 new_data[field_name] = result_line[field_name]
270 self._data[result_line['id']].update(new_data)
272 if not name in self._data[self._id]:
273 #how did this happen?
274 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
275 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
276 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
277 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
278 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
279 return self._data[self._id][name]
281 def __getattr__(self, name):
285 raise AttributeError(e)
287 def __contains__(self, name):
288 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
290 def __hasattr__(self, name):
297 return "browse_record(%s, %d)" % (self._table_name, self._id)
299 def __eq__(self, other):
300 if not isinstance(other, browse_record):
302 return (self._table_name, self._id) == (other._table_name, other._id)
304 def __ne__(self, other):
305 if not isinstance(other, browse_record):
307 return (self._table_name, self._id) != (other._table_name, other._id)
309 # we need to define __unicode__ even though we've already defined __str__
310 # because we have overridden __getattr__
311 def __unicode__(self):
312 return unicode(str(self))
315 return hash((self._table_name, self._id))
323 (type returned by postgres when the column was created, type expression to create the column)
327 fields.boolean: 'bool',
328 fields.integer: 'int4',
329 fields.integer_big: 'int8',
333 fields.datetime: 'timestamp',
334 fields.binary: 'bytea',
335 fields.many2one: 'int4',
337 if type(f) in type_dict:
338 f_type = (type_dict[type(f)], type_dict[type(f)])
339 elif isinstance(f, fields.float):
341 f_type = ('numeric', 'NUMERIC')
343 f_type = ('float8', 'DOUBLE PRECISION')
344 elif isinstance(f, (fields.char, fields.reference)):
345 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
346 elif isinstance(f, fields.selection):
347 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
348 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
349 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
352 f_size = getattr(f, 'size', None) or 16
355 f_type = ('int4', 'INTEGER')
357 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
358 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
359 t = eval('fields.'+(f._type), globals())
360 f_type = (type_dict[t], type_dict[t])
361 elif isinstance(f, fields.function) and f._type == 'float':
363 f_type = ('numeric', 'NUMERIC')
365 f_type = ('float8', 'DOUBLE PRECISION')
366 elif isinstance(f, fields.function) and f._type == 'selection':
367 f_type = ('text', 'text')
368 elif isinstance(f, fields.function) and f._type == 'char':
369 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
371 logger = netsvc.Logger()
372 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
377 class orm_template(object):
383 _parent_name = 'parent_id'
384 _parent_store = False
385 _parent_order = False
395 CONCURRENCY_CHECK_FIELD = '__last_update'
396 def log(self, cr, uid, id, message, secondary=False, context=None):
397 return self.pool.get('res.log').create(cr, uid,
400 'res_model': self._name,
401 'secondary': secondary,
407 def view_init(self, cr, uid, fields_list, context=None):
408 """Override this method to do specific things when a view on the object is opened."""
411 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
412 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
414 def _field_create(self, cr, context=None):
417 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
419 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
420 model_id = cr.fetchone()[0]
421 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'))
423 model_id = cr.fetchone()[0]
424 if 'module' in context:
425 name_id = 'model_'+self._name.replace('.', '_')
426 cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
428 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
429 (name_id, context['module'], 'ir.model', model_id)
434 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
436 for rec in cr.dictfetchall():
437 cols[rec['name']] = rec
439 for (k, f) in self._columns.items():
441 'model_id': model_id,
444 'field_description': f.string.replace("'", " "),
446 'relation': f._obj or '',
447 'view_load': (f.view_load and 1) or 0,
448 'select_level': tools.ustr(f.select or 0),
449 'readonly': (f.readonly and 1) or 0,
450 'required': (f.required and 1) or 0,
451 'selectable': (f.selectable and 1) or 0,
452 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
454 # When its a custom field,it does not contain f.select
455 if context.get('field_state', 'base') == 'manual':
456 if context.get('field_name', '') == k:
457 vals['select_level'] = context.get('select', '0')
458 #setting value to let the problem NOT occur next time
460 vals['select_level'] = cols[k]['select_level']
463 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
464 id = cr.fetchone()[0]
466 cr.execute("""INSERT INTO ir_model_fields (
467 id, model_id, model, name, field_description, ttype,
468 relation,view_load,state,select_level,relation_field
470 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
472 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
473 vals['relation'], bool(vals['view_load']), 'base',
474 vals['select_level'], vals['relation_field']
476 if 'module' in context:
477 name1 = 'field_' + self._table + '_' + k
478 cr.execute("select name from ir_model_data where name=%s", (name1,))
480 name1 = name1 + "_" + str(id)
481 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
482 (name1, context['module'], 'ir.model.fields', id)
485 for key, val in vals.items():
486 if cols[k][key] != vals[key]:
487 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
489 cr.execute("""UPDATE ir_model_fields SET
490 model_id=%s, field_description=%s, ttype=%s, relation=%s,
491 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
493 model=%s AND name=%s""", (
494 vals['model_id'], vals['field_description'], vals['ttype'],
495 vals['relation'], bool(vals['view_load']),
496 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
501 def _auto_init(self, cr, context=None):
502 self._field_create(cr, context=context)
504 def __init__(self, cr):
505 if not self._name and not hasattr(self, '_inherit'):
506 name = type(self).__name__.split('.')[0]
507 msg = "The class %s has to have a _name attribute" % name
509 logger = netsvc.Logger()
510 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
511 raise except_orm('ValueError', msg)
513 if not self._description:
514 self._description = self._name
516 self._table = self._name.replace('.', '_')
518 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
519 """Fetch records as objects allowing to use dot notation to browse fields and relations
521 :param cr: database cursor
522 :param user: current user id
523 :param select: id or list of ids
524 :param context: context arguments, like lang, time zone
525 :rtype: object or list of objects requested
528 self._list_class = list_class or browse_record_list
530 # need to accepts ints and longs because ids coming from a method
531 # launched by button in the interface have a type long...
532 if isinstance(select, (int, long)):
533 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
534 elif isinstance(select, list):
535 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)
539 def __export_row(self, cr, uid, row, fields, context=None):
543 def check_type(field_type):
544 if field_type == 'float':
546 elif field_type == 'integer':
548 elif field_type == 'boolean':
552 def selection_field(in_field):
553 col_obj = self.pool.get(in_field.keys()[0])
554 if f[i] in col_obj._columns.keys():
555 return col_obj._columns[f[i]]
556 elif f[i] in col_obj._inherits.keys():
557 selection_field(col_obj._inherits)
562 data = map(lambda x: '', range(len(fields)))
564 for fpos in range(len(fields)):
573 model_data = self.pool.get('ir.model.data')
574 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
576 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
578 r = '%s.%s' % (d['module'], d['name'])
585 # To display external name of selection field when its exported
587 if f[i] in self._columns.keys():
588 cols = self._columns[f[i]]
589 elif f[i] in self._inherit_fields.keys():
590 cols = selection_field(self._inherits)
591 if cols and cols._type == 'selection':
592 sel_list = cols.selection
593 if r and type(sel_list) == type([]):
594 r = [x[1] for x in sel_list if r==x[0]]
595 r = r and r[0] or False
597 if f[i] in self._columns:
598 r = check_type(self._columns[f[i]]._type)
599 elif f[i] in self._inherit_fields:
600 r = check_type(self._inherit_fields[f[i]][2]._type)
601 data[fpos] = r or False
603 if isinstance(r, (browse_record_list, list)):
605 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
608 if [x for x in fields2 if x]:
612 lines2 = self.__export_row(cr, uid, row2, fields2,
615 for fpos2 in range(len(fields)):
616 if lines2 and lines2[0][fpos2]:
617 data[fpos2] = lines2[0][fpos2]
621 if isinstance(rr.name, browse_record):
623 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
624 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
625 dt += tools.ustr(rr_name or '') + ','
635 if isinstance(r, browse_record):
636 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
637 r = r and r[0] and r[0][1] or ''
638 data[fpos] = tools.ustr(r or '')
639 return [data] + lines
641 def export_data(self, cr, uid, ids, fields_to_export, context=None):
643 Export fields for selected objects
645 :param cr: database cursor
646 :param uid: current user id
647 :param ids: list of ids
648 :param fields_to_export: list of fields
649 :param context: context arguments, like lang, time zone
650 :rtype: dictionary with a *datas* matrix
652 This method is used when exporting data via client menu
657 cols = self._columns.copy()
658 for f in self._inherit_fields:
659 cols.update({f: self._inherit_fields[f][2]})
661 if x=='.id': return [x]
662 return x.replace(':id','/id').replace('.id','/.id').split('/')
663 fields_to_export = map(fsplit, fields_to_export)
664 fields_export = fields_to_export + []
668 for row in self.browse(cr, uid, ids, context):
669 datas += self.__export_row(cr, uid, row, fields_to_export, context)
670 return {'datas': datas}
672 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
674 Import given data in given module
676 :param cr: database cursor
677 :param uid: current user id
678 :param fields: list of fields
679 :param data: data to import
680 :param mode: 'init' or 'update' for record creation
681 :param current_module: module name
682 :param noupdate: flag for record creation
683 :param context: context arguments, like lang, time zone,
684 :param filename: optional file to store partial import state for recovery
687 This method is used when importing data via client menu.
689 Example of fields to import for a sale.order::
692 partner_id, (=name_search)
693 order_line/.id, (=database_id)
695 order_line/product_id/id, (=xml id)
696 order_line/price_unit,
697 order_line/product_uom_qty,
698 order_line/product_uom/id (=xml_id)
702 def _replace_field(x):
703 x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
704 return x.replace(':id','/id').split('/')
705 fields = map(_replace_field, fields)
706 logger = netsvc.Logger()
707 ir_model_data_obj = self.pool.get('ir.model.data')
709 # mode: id (XML id) or .id (database id) or False for name_get
710 def _get_id(model_name, id, current_module=False, mode='id'):
713 obj_model = self.pool.get(model_name)
714 ids = obj_model.search(cr, uid, [('id', '=', int(id))])
716 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
719 module, xml_id = id.rsplit('.', 1)
721 module, xml_id = current_module, id
722 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
723 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
724 if not ir_model_data:
725 raise ValueError('No references to %s.%s' % (module, xml_id))
726 id = ir_model_data[0]['res_id']
728 obj_model = self.pool.get(model_name)
729 ids = obj_model.name_search(cr, uid, id, operator='=')
731 raise ValueError('No record found for %s' % (id,))
736 # datas: a list of records, each record is defined by a list of values
737 # prefix: a list of prefix fields ['line_ids']
738 # position: the line to process, skip is False if it's the first line of the current record
740 # (res, position, warning, res_id) with
741 # res: the record for the next line to process (including it's one2many)
742 # position: the new position for the next line
743 # res_id: the ID of the record if it's a modification
744 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
745 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':
769 data_res_id = _get_id(model_name, line[i], current_module, 'id')
770 except ValueError, e:
775 # ID of the record using a database ID
776 elif field[len(prefix)]=='.id':
777 data_res_id = _get_id(model_name, line[i], current_module, '.id')
780 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
781 if fields_def[field[len(prefix)]]['type']=='one2many':
782 if field[len(prefix)] in done:
784 done[field[len(prefix)]] = True
785 relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
786 newfd = relation_obj.fields_get( cr, uid, context=context )
790 while pos < len(datas):
791 res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
794 (newrow, pos, w2, data_res_id2, xml_id2) = res2
795 nbrmax = max(nbrmax, pos)
798 if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
800 res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
802 elif fields_def[field[len(prefix)]]['type']=='many2one':
803 relation = fields_def[field[len(prefix)]]['relation']
804 if len(field) == len(prefix)+1:
807 mode = field[len(prefix)+1]
808 res = _get_id(relation, line[i], current_module, mode)
810 elif fields_def[field[len(prefix)]]['type']=='many2many':
811 relation = fields_def[field[len(prefix)]]['relation']
812 if len(field) == len(prefix)+1:
815 mode = field[len(prefix)+1]
817 # TODO: improve this by using csv.csv_reader
819 for db_id in line[i].split(config.get('csv_internal_sep')):
820 res.append( _get_id(relation, db_id, current_module, mode) )
823 elif fields_def[field[len(prefix)]]['type'] == 'integer':
824 res = line[i] and int(line[i]) or 0
825 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
826 res = line[i].lower() not in ('0', 'false', 'off')
827 elif fields_def[field[len(prefix)]]['type'] == 'float':
828 res = line[i] and float(line[i]) or 0.0
829 elif fields_def[field[len(prefix)]]['type'] == 'selection':
830 for key, val in fields_def[field[len(prefix)]]['selection']:
831 if line[i] in [tools.ustr(key), tools.ustr(val)]:
834 if line[i] and not res:
835 logger.notifyChannel("import", netsvc.LOG_WARNING,
836 _("key '%s' not found in selection field '%s'") % \
837 (line[i], field[len(prefix)]))
838 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
842 row[field[len(prefix)]] = res or False
844 result = (row, nbrmax, warning, data_res_id, xml_id)
847 fields_def = self.fields_get(cr, uid, context=context)
849 if config.get('import_partial', False) and filename:
850 data = pickle.load(file(config.get('import_partial')))
851 original_value = data.get(filename, 0)
854 while position<len(datas):
857 (res, position, warning, res_id, xml_id) = \
858 process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
861 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
864 id = ir_model_data_obj._update(cr, uid, self._name,
865 current_module, res, mode=mode, xml_id=xml_id,
866 noupdate=noupdate, res_id=res_id, context=context)
868 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
870 if config.get('import_partial', False) and filename and (not (position%100)):
871 data = pickle.load(file(config.get('import_partial')))
872 data[filename] = position
873 pickle.dump(data, file(config.get('import_partial'), 'wb'))
874 if context.get('defer_parent_store_computation'):
875 self._parent_store_compute(cr)
878 if context.get('defer_parent_store_computation'):
879 self._parent_store_compute(cr)
880 return (position, 0, 0, 0)
882 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
884 Read records with given ids with the given fields
886 :param cr: database cursor
887 :param user: current user id
888 :param ids: id or list of the ids of the records to read
889 :param fields: optional list of field names to return (default: all fields would be returned)
890 :type fields: list (example ['field_name_1', ...])
891 :param context: optional context dictionary - it may contains keys for specifying certain options
892 like ``context_lang``, ``context_tz`` to alter the results of the call.
893 A special ``bin_size`` boolean flag may also be passed in the context to request the
894 value of all fields.binary columns to be returned as the size of the binary instead of its
895 contents. This can also be selectively overriden by passing a field-specific flag
896 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
897 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
898 :return: list of dictionaries((dictionary per record asked)) with requested field values
899 :rtype: [{‘name_of_the_field’: value, ...}, ...]
900 :raise AccessError: * if user has no read rights on the requested object
901 * if user tries to bypass access rules for read on the requested object
904 raise NotImplementedError(_('The read method is not implemented on this object !'))
906 def get_invalid_fields(self, cr, uid):
907 return list(self._invalids)
909 def _validate(self, cr, uid, ids, context=None):
910 context = context or {}
911 lng = context.get('lang', False) or 'en_US'
912 trans = self.pool.get('ir.translation')
914 for constraint in self._constraints:
915 fun, msg, fields = constraint
916 if not fun(self, cr, uid, ids):
917 # Check presence of __call__ directly instead of using
918 # callable() because it will be deprecated as of Python 3.0
919 if hasattr(msg, '__call__'):
920 tmp_msg = msg(self, cr, uid, ids, context=context)
921 if isinstance(tmp_msg, tuple):
922 tmp_msg, params = tmp_msg
923 translated_msg = tmp_msg % params
925 translated_msg = tmp_msg
927 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
929 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
931 self._invalids.update(fields)
934 raise except_orm('ValidateError', '\n'.join(error_msgs))
936 self._invalids.clear()
938 def default_get(self, cr, uid, fields_list, context=None):
940 Returns default values for the fields in fields_list.
942 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
943 :type fields_list: list
944 :param context: optional context dictionary - it may contains keys for specifying certain options
945 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
946 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
947 or override a default value for a field.
948 A special ``bin_size`` boolean flag may also be passed in the context to request the
949 value of all fields.binary columns to be returned as the size of the binary instead of its
950 contents. This can also be selectively overriden by passing a field-specific flag
951 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
952 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
953 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
955 # trigger view init hook
956 self.view_init(cr, uid, fields_list, context)
962 # get the default values for the inherited fields
963 for t in self._inherits.keys():
964 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
967 # get the default values defined in the object
968 for f in fields_list:
969 if f in self._defaults:
970 if callable(self._defaults[f]):
971 defaults[f] = self._defaults[f](self, cr, uid, context)
973 defaults[f] = self._defaults[f]
975 fld_def = ((f in self._columns) and self._columns[f]) \
976 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
979 if isinstance(fld_def, fields.property):
980 property_obj = self.pool.get('ir.property')
981 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
983 if isinstance(prop_value, (browse_record, browse_null)):
984 defaults[f] = prop_value.id
986 defaults[f] = prop_value
988 if f not in defaults:
991 # get the default values set by the user and override the default
992 # values defined in the object
993 ir_values_obj = self.pool.get('ir.values')
994 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
995 for id, field, field_value in res:
996 if field in fields_list:
997 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
998 if fld_def._type in ('many2one', 'one2one'):
999 obj = self.pool.get(fld_def._obj)
1000 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1002 if fld_def._type in ('many2many'):
1003 obj = self.pool.get(fld_def._obj)
1005 for i in range(len(field_value)):
1006 if not obj.search(cr, uid, [('id', '=',
1009 field_value2.append(field_value[i])
1010 field_value = field_value2
1011 if fld_def._type in ('one2many'):
1012 obj = self.pool.get(fld_def._obj)
1014 for i in range(len(field_value)):
1015 field_value2.append({})
1016 for field2 in field_value[i]:
1017 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1018 obj2 = self.pool.get(obj._columns[field2]._obj)
1019 if not obj2.search(cr, uid,
1020 [('id', '=', field_value[i][field2])]):
1022 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1023 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1024 if not obj2.search(cr, uid,
1025 [('id', '=', field_value[i][field2])]):
1027 # TODO add test for many2many and one2many
1028 field_value2[i][field2] = field_value[i][field2]
1029 field_value = field_value2
1030 defaults[field] = field_value
1032 # get the default values from the context
1033 for key in context or {}:
1034 if key.startswith('default_') and (key[8:] in fields_list):
1035 defaults[key[8:]] = context[key]
1039 def perm_read(self, cr, user, ids, context=None, details=True):
1040 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1042 def unlink(self, cr, uid, ids, context=None):
1043 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1045 def write(self, cr, user, ids, vals, context=None):
1046 raise NotImplementedError(_('The write method is not implemented on this object !'))
1048 def create(self, cr, user, vals, context=None):
1049 raise NotImplementedError(_('The create method is not implemented on this object !'))
1051 def fields_get_keys(self, cr, user, context=None):
1052 res = self._columns.keys()
1053 for parent in self._inherits:
1054 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1057 # returns the definition of each field in the object
1058 # the optional fields parameter can limit the result to some fields
1059 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1063 translation_obj = self.pool.get('ir.translation')
1064 for parent in self._inherits:
1065 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1067 if self._columns.keys():
1068 for f in self._columns.keys():
1069 field_col = self._columns[f]
1070 if allfields and f not in allfields:
1072 res[f] = {'type': field_col._type}
1073 # This additional attributes for M2M and function field is added
1074 # because we need to display tooltip with this additional information
1075 # when client is started in debug mode.
1076 if isinstance(field_col, fields.function):
1077 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1078 res[f]['store'] = field_col.store
1079 if isinstance(field_col.store, dict):
1080 res[f]['store'] = str(field_col.store)
1081 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1082 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1083 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1084 res[f]['func_obj'] = field_col._obj or False
1085 res[f]['func_method'] = field_col._method
1086 if isinstance(field_col, fields.many2many):
1087 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1088 res[f]['third_table'] = field_col._rel
1089 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1090 'change_default', 'translate', 'help', 'select', 'selectable'):
1091 if getattr(field_col, arg):
1092 res[f][arg] = getattr(field_col, arg)
1093 if not write_access:
1094 res[f]['readonly'] = True
1095 res[f]['states'] = {}
1096 for arg in ('digits', 'invisible', 'filters'):
1097 if getattr(field_col, arg, None):
1098 res[f][arg] = getattr(field_col, arg)
1100 if field_col.string:
1101 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1103 res[f]['string'] = res_trans
1105 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1107 res[f]['help'] = help_trans
1109 if hasattr(field_col, 'selection'):
1110 if isinstance(field_col.selection, (tuple, list)):
1111 sel = field_col.selection
1112 # translate each selection option
1114 for (key, val) in sel:
1117 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1118 sel2.append((key, val2 or val))
1120 res[f]['selection'] = sel
1122 # call the 'dynamic selection' function
1123 res[f]['selection'] = field_col.selection(self, cr, user, context)
1124 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1125 res[f]['relation'] = field_col._obj
1126 res[f]['domain'] = field_col._domain
1127 res[f]['context'] = field_col._context
1129 #TODO : read the fields from the database
1133 # filter out fields which aren't in the fields list
1134 for r in res.keys():
1135 if r not in allfields:
1140 # Overload this method if you need a window title which depends on the context
1142 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1145 def __view_look_dom(self, cr, user, node, view_id, context=None):
1153 if isinstance(s, unicode):
1154 return s.encode('utf8')
1157 # return True if node can be displayed to current user
1158 def check_group(node):
1159 if node.get('groups'):
1160 groups = node.get('groups').split(',')
1161 access_pool = self.pool.get('ir.model.access')
1162 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1164 node.set('invisible', '1')
1165 if 'attrs' in node.attrib:
1166 del(node.attrib['attrs']) #avoid making field visible later
1167 del(node.attrib['groups'])
1172 if node.tag in ('field', 'node', 'arrow'):
1173 if node.get('object'):
1178 if f.tag in ('field'):
1179 xml += etree.tostring(f, encoding="utf-8")
1181 new_xml = etree.fromstring(encode(xml))
1182 ctx = context.copy()
1183 ctx['base_model_name'] = self._name
1184 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1189 attrs = {'views': views}
1191 if node.get('name'):
1194 if node.get('name') in self._columns:
1195 column = self._columns[node.get('name')]
1197 column = self._inherit_fields[node.get('name')][2]
1202 relation = self.pool.get(column._obj)
1207 if f.tag in ('form', 'tree', 'graph'):
1209 ctx = context.copy()
1210 ctx['base_model_name'] = self._name
1211 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1212 views[str(f.tag)] = {
1216 attrs = {'views': views}
1217 if node.get('widget') and node.get('widget') == 'selection':
1218 # Prepare the cached selection list for the client. This needs to be
1219 # done even when the field is invisible to the current user, because
1220 # other events could need to change its value to any of the selectable ones
1221 # (such as on_change events, refreshes, etc.)
1223 # If domain and context are strings, we keep them for client-side, otherwise
1224 # we evaluate them server-side to consider them when generating the list of
1226 # TODO: find a way to remove this hack, by allow dynamic domains
1228 if column._domain and not isinstance(column._domain, basestring):
1229 dom = column._domain
1230 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1231 search_context = dict(context)
1232 if column._context and not isinstance(column._context, basestring):
1233 search_context.update(column._context)
1234 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1235 if (node.get('required') and not int(node.get('required'))) or not column.required:
1236 attrs['selection'].append((False, ''))
1237 fields[node.get('name')] = attrs
1239 elif node.tag in ('form', 'tree'):
1240 result = self.view_header_get(cr, user, False, node.tag, context)
1242 node.set('string', result)
1244 elif node.tag == 'calendar':
1245 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1246 if node.get(additional_field):
1247 fields[node.get(additional_field)] = {}
1249 if 'groups' in node.attrib:
1253 if ('lang' in context) and not result:
1254 if node.get('string'):
1255 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1256 if trans == node.get('string') and ('base_model_name' in context):
1257 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1258 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1259 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1261 node.set('string', trans)
1262 if node.get('confirm'):
1263 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1265 node.set('confirm', trans)
1267 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1269 node.set('sum', trans)
1272 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1273 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1277 def _disable_workflow_buttons(self, cr, user, node):
1279 # admin user can always activate workflow buttons
1282 # TODO handle the case of more than one workflow for a model or multiple
1283 # transitions with different groups and same signal
1284 usersobj = self.pool.get('res.users')
1285 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1286 for button in buttons:
1287 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1288 cr.execute("""SELECT DISTINCT t.group_id
1290 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1291 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1294 AND t.group_id is NOT NULL
1295 """, (self._name, button.get('name')))
1296 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1297 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1298 button.set('readonly', str(int(not can_click)))
1301 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1302 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1303 node = self._disable_workflow_buttons(cr, user, node)
1304 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1306 if node.tag == 'diagram':
1307 if node.getchildren()[0].tag == 'node':
1308 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1309 if node.getchildren()[1].tag == 'arrow':
1310 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1311 for key, value in node_fields.items():
1313 for key, value in arrow_fields.items():
1316 fields = self.fields_get(cr, user, fields_def.keys(), context)
1317 for field in fields_def:
1319 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1320 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1321 elif field in fields:
1322 fields[field].update(fields_def[field])
1324 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))
1325 res = cr.fetchall()[:]
1327 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1328 msg = "\n * ".join([r[0] for r in res])
1329 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1330 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1331 raise except_orm('View error', msg)
1334 def __get_default_calendar_view(self):
1335 """Generate a default calendar view (For internal use only).
1338 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1339 '<calendar string="%s"') % (self._description)
1341 if (self._date_name not in self._columns):
1343 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1344 if dt in self._columns:
1345 self._date_name = dt
1350 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1353 arch += ' date_start="%s"' % (self._date_name)
1355 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1356 if color in self._columns:
1357 arch += ' color="' + color + '"'
1360 dt_stop_flag = False
1362 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1363 if dt_stop in self._columns:
1364 arch += ' date_stop="' + dt_stop + '"'
1368 if not dt_stop_flag:
1369 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1370 if dt_delay in self._columns:
1371 arch += ' date_delay="' + dt_delay + '"'
1375 ' <field name="%s"/>\n'
1376 '</calendar>') % (self._rec_name)
1380 def __get_default_search_view(self, cr, uid, context=None):
1381 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1382 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1384 fields_to_search = set()
1385 fields = self.fields_get(cr, uid, context=context)
1386 for field in fields:
1387 if fields[field].get('select'):
1388 fields_to_search.add(field)
1389 for view in (form_view, tree_view):
1390 view_root = etree.fromstring(view['arch'])
1391 # Only care about select=1 in xpath below, because select=2 is covered
1392 # by the custom advanced search in clients
1393 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1395 tree_view_root = view_root # as provided by loop above
1396 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1397 field_group = etree.Element("group")
1398 search_view.append(field_group)
1400 for field_name in fields_to_search:
1401 field_group.append(etree.Element("field", attrib={'name': field_name}))
1403 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1406 # if view_id, view_type is not required
1408 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1410 Get the detailed composition of the requested view like fields, model, view architecture
1412 :param cr: database cursor
1413 :param user: current user id
1414 :param view_id: id of the view or None
1415 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1416 :param context: context arguments, like lang, time zone
1417 :param toolbar: true to include contextual actions
1418 :param submenu: example (portal_project module)
1419 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1420 :raise AttributeError:
1421 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1422 * if some tag other than 'position' is found in parent view
1423 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1430 if isinstance(s, unicode):
1431 return s.encode('utf8')
1434 def raise_view_error(error_msg, child_view_id):
1435 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1436 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1437 % { 'xml_id': child_view.xml_id,
1438 'parent_xml_id': view.xml_id,
1439 'model': self._name, })
1441 def _inherit_apply(src, inherit, inherit_id=None):
1442 def _find(node, node2):
1443 if node2.tag == 'xpath':
1444 res = node.xpath(node2.get('expr'))
1450 for n in node.getiterator(node2.tag):
1452 if node2.tag == 'field':
1453 # only compare field names, a field can be only once in a given view
1454 # at a given level (and for multilevel expressions, we should use xpath
1455 # inheritance spec anyway)
1456 if node2.get('name') == n.get('name'):
1460 for attr in node2.attrib:
1461 if attr == 'position':
1464 if n.get(attr) == node2.get(attr):
1471 # End: _find(node, node2)
1473 doc_dest = etree.fromstring(encode(inherit))
1474 toparse = [doc_dest]
1477 node2 = toparse.pop(0)
1478 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1480 if node2.tag == 'data':
1481 toparse += [ c for c in doc_dest ]
1483 node = _find(src, node2)
1484 if node is not None:
1486 if node2.get('position'):
1487 pos = node2.get('position')
1488 if pos == 'replace':
1489 parent = node.getparent()
1491 src = copy.deepcopy(node2[0])
1494 node.addprevious(child)
1495 node.getparent().remove(node)
1496 elif pos == 'attributes':
1497 for child in node2.getiterator('attribute'):
1498 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1500 node.set(attribute[0], attribute[1])
1502 del(node.attrib[attribute[0]])
1504 sib = node.getnext()
1508 elif pos == 'after':
1513 sib.addprevious(child)
1514 elif pos == 'before':
1515 node.addprevious(child)
1517 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1520 ' %s="%s"' % (attr, node2.get(attr))
1521 for attr in node2.attrib
1522 if attr != 'position'
1524 tag = "<%s%s>" % (node2.tag, attrs)
1525 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1527 # End: _inherit_apply(src, inherit)
1529 result = {'type': view_type, 'model': self._name}
1534 parent_view_model = None
1536 view_ref = context.get(view_type + '_view_ref', False)
1537 if view_ref and not view_id:
1539 module, view_ref = view_ref.split('.', 1)
1540 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1541 view_ref_res = cr.fetchone()
1543 view_id = view_ref_res[0]
1546 query = "SELECT arch,name,field_parent,id,type,inherit_id,model FROM ir_ui_view WHERE id=%s"
1549 query += " AND model=%s"
1550 params += (self._name,)
1551 cr.execute(query, params)
1553 cr.execute('''SELECT
1554 arch,name,field_parent,id,type,inherit_id,model
1561 ORDER BY priority''', (self._name, view_type))
1562 sql_res = cr.fetchone()
1568 view_id = ok or sql_res[3]
1570 parent_view_model = sql_res[6]
1572 # if a view was found
1574 result['type'] = sql_res[4]
1575 result['view_id'] = sql_res[3]
1576 result['arch'] = sql_res[0]
1578 def _inherit_apply_rec(result, inherit_id):
1579 # get all views which inherit from (ie modify) this view
1580 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1581 sql_inherit = cr.fetchall()
1582 for (inherit, id) in sql_inherit:
1583 result = _inherit_apply(result, inherit, id)
1584 result = _inherit_apply_rec(result, id)
1587 inherit_result = etree.fromstring(encode(result['arch']))
1588 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1590 result['name'] = sql_res[1]
1591 result['field_parent'] = sql_res[2] or False
1594 # otherwise, build some kind of default view
1595 if view_type == 'form':
1596 res = self.fields_get(cr, user, context=context)
1597 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1598 '<form string="%s">' % (self._description,)
1600 if res[x]['type'] not in ('one2many', 'many2many'):
1601 xml += '<field name="%s"/>' % (x,)
1602 if res[x]['type'] == 'text':
1606 elif view_type == 'tree':
1607 _rec_name = self._rec_name
1608 if _rec_name not in self._columns:
1609 _rec_name = self._columns.keys()[0]
1610 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1611 '<tree string="%s"><field name="%s"/></tree>' \
1612 % (self._description, self._rec_name)
1614 elif view_type == 'calendar':
1615 xml = self.__get_default_calendar_view()
1617 elif view_type == 'search':
1618 xml = self.__get_default_search_view(cr, user, context)
1621 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1622 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1623 result['arch'] = etree.fromstring(encode(xml))
1624 result['name'] = 'default'
1625 result['field_parent'] = False
1626 result['view_id'] = 0
1628 if parent_view_model != self._name:
1629 ctx = context.copy()
1630 ctx['base_model_name'] = parent_view_model
1633 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1634 result['arch'] = xarch
1635 result['fields'] = xfields
1638 if context and context.get('active_id', False):
1639 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1641 act_id = data_menu.id
1643 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1644 result['submenu'] = getattr(data_action, 'menus', False)
1648 for key in ('report_sxw_content', 'report_rml_content',
1649 'report_sxw', 'report_rml',
1650 'report_sxw_content_data', 'report_rml_content_data'):
1654 ir_values_obj = self.pool.get('ir.values')
1655 resprint = ir_values_obj.get(cr, user, 'action',
1656 'client_print_multi', [(self._name, False)], False,
1658 resaction = ir_values_obj.get(cr, user, 'action',
1659 'client_action_multi', [(self._name, False)], False,
1662 resrelate = ir_values_obj.get(cr, user, 'action',
1663 'client_action_relate', [(self._name, False)], False,
1665 resprint = map(clean, resprint)
1666 resaction = map(clean, resaction)
1667 resaction = filter(lambda x: not x.get('multi', False), resaction)
1668 resprint = filter(lambda x: not x.get('multi', False), resprint)
1669 resrelate = map(lambda x: x[2], resrelate)
1671 for x in resprint + resaction + resrelate:
1672 x['string'] = x['name']
1674 result['toolbar'] = {
1676 'action': resaction,
1681 _view_look_dom_arch = __view_look_dom_arch
1683 def search_count(self, cr, user, args, context=None):
1686 res = self.search(cr, user, args, context=context, count=True)
1687 if isinstance(res, list):
1691 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1693 Search for records based on a search domain.
1695 :param cr: database cursor
1696 :param user: current user id
1697 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1698 :param offset: optional number of results to skip in the returned values (default: 0)
1699 :param limit: optional max number of records to return (default: **None**)
1700 :param order: optional columns to sort by (default: self._order=id )
1701 :param context: optional context arguments, like lang, time zone
1702 :type context: dictionary
1703 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1704 :return: id or list of ids of records matching the criteria
1705 :rtype: integer or list of integers
1706 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1708 **Expressing a search domain (args)**
1710 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1712 * **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.
1713 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1714 The semantics of most of these operators are obvious.
1715 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1716 according to the semantics of this model (i.e following the relationship field named by
1717 ``self._parent_name``, by default ``parent_id``.
1718 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1720 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1721 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1722 Be very careful about this when you combine them the first time.
1724 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1726 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1728 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::
1730 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1733 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1735 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1737 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1738 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1739 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1741 :param access_rights_uid: optional user ID to use when checking access rights
1742 (not for ir.rules, this is only for ir.model.access)
1744 raise NotImplementedError(_('The search method is not implemented on this object !'))
1746 def name_get(self, cr, user, ids, context=None):
1749 :param cr: database cursor
1750 :param user: current user id
1752 :param ids: list of ids
1753 :param context: context arguments, like lang, time zone
1754 :type context: dictionary
1755 :return: tuples with the text representation of requested objects for to-many relationships
1762 if isinstance(ids, (int, long)):
1764 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1765 [self._rec_name], context, load='_classic_write')]
1767 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1769 Search for records and their display names according to a search domain.
1771 :param cr: database cursor
1772 :param user: current user id
1773 :param name: object name to search
1774 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1775 :param operator: operator for search criterion
1776 :param context: context arguments, like lang, time zone
1777 :type context: dictionary
1778 :param limit: optional max number of records to return
1779 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1781 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1782 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1785 return self._name_search(cr, user, name, args, operator, context, limit)
1787 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1788 # solve some access rights issues
1789 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1796 args += [(self._rec_name, operator, name)]
1797 access_rights_uid = name_get_uid or user
1798 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1799 res = self.name_get(cr, access_rights_uid, ids, context)
1802 def copy(self, cr, uid, id, default=None, context=None):
1803 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1805 def exists(self, cr, uid, id, context=None):
1806 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1808 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1811 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1813 fields = self._columns.keys() + self._inherit_fields.keys()
1814 #FIXME: collect all calls to _get_source into one SQL call.
1816 res[lang] = {'code': lang}
1818 if f in self._columns:
1819 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1821 res[lang][f] = res_trans
1823 res[lang][f] = self._columns[f].string
1824 for table in self._inherits:
1825 cols = intersect(self._inherit_fields.keys(), fields)
1826 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1829 res[lang]['code'] = lang
1830 for f in res2[lang]:
1831 res[lang][f] = res2[lang][f]
1834 def write_string(self, cr, uid, id, langs, vals, context=None):
1835 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1836 #FIXME: try to only call the translation in one SQL
1839 if field in self._columns:
1840 src = self._columns[field].string
1841 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1842 for table in self._inherits:
1843 cols = intersect(self._inherit_fields.keys(), vals)
1845 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1848 def _check_removed_columns(self, cr, log=False):
1849 raise NotImplementedError()
1851 def _add_missing_default_values(self, cr, uid, values, context=None):
1852 missing_defaults = []
1853 avoid_tables = [] # avoid overriding inherited values when parent is set
1854 for tables, parent_field in self._inherits.items():
1855 if parent_field in values:
1856 avoid_tables.append(tables)
1857 for field in self._columns.keys():
1858 if not field in values:
1859 missing_defaults.append(field)
1860 for field in self._inherit_fields.keys():
1861 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
1862 missing_defaults.append(field)
1864 if len(missing_defaults):
1865 # override defaults with the provided values, never allow the other way around
1866 defaults = self.default_get(cr, uid, missing_defaults, context)
1868 if (dv in self._columns and self._columns[dv]._type == 'many2many') \
1869 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many') \
1870 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
1871 defaults[dv] = [(6, 0, defaults[dv])]
1872 if dv in self._columns and self._columns[dv]._type == 'one2many' \
1873 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many') \
1874 and isinstance(defaults[dv], (list, tuple)) and isinstance(defaults[dv][0], dict):
1875 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
1876 defaults.update(values)
1880 class orm_memory(orm_template):
1882 _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']
1883 _inherit_fields = {}
1888 def __init__(self, cr):
1889 super(orm_memory, self).__init__(cr)
1893 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
1895 def _check_access(self, uid, object_id, mode):
1896 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
1897 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
1899 def vaccum(self, cr, uid):
1901 if self.check_id % self._check_time:
1904 max = time.time() - self._max_hours * 60 * 60
1905 for id in self.datas:
1906 if self.datas[id]['internal.date_access'] < max:
1908 self.unlink(cr, 1, tounlink)
1909 if len(self.datas) > self._max_count:
1910 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
1912 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
1913 self.unlink(cr, uid, ids)
1916 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
1919 if not fields_to_read:
1920 fields_to_read = self._columns.keys()
1924 if isinstance(ids, (int, long)):
1928 for f in fields_to_read:
1929 record = self.datas.get(id)
1931 self._check_access(user, id, 'read')
1932 r[f] = record.get(f, False)
1933 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
1936 if id in self.datas:
1937 self.datas[id]['internal.date_access'] = time.time()
1938 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
1939 for f in fields_post:
1940 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
1941 for record in result:
1942 record[f] = res2[record['id']]
1943 if isinstance(ids_orig, (int, long)):
1947 def write(self, cr, user, ids, vals, context=None):
1953 if self._columns[field]._classic_write:
1954 vals2[field] = vals[field]
1956 upd_todo.append(field)
1957 for object_id in ids:
1958 self._check_access(user, object_id, mode='write')
1959 self.datas[object_id].update(vals2)
1960 self.datas[object_id]['internal.date_access'] = time.time()
1961 for field in upd_todo:
1962 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
1963 self._validate(cr, user, [object_id], context)
1964 wf_service = netsvc.LocalService("workflow")
1965 wf_service.trg_write(user, self._name, object_id, cr)
1968 def create(self, cr, user, vals, context=None):
1969 self.vaccum(cr, user)
1971 id_new = self.next_id
1973 vals = self._add_missing_default_values(cr, user, vals, context)
1978 if self._columns[field]._classic_write:
1979 vals2[field] = vals[field]
1981 upd_todo.append(field)
1982 self.datas[id_new] = vals2
1983 self.datas[id_new]['internal.date_access'] = time.time()
1984 self.datas[id_new]['internal.create_uid'] = user
1986 for field in upd_todo:
1987 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
1988 self._validate(cr, user, [id_new], context)
1989 if self._log_create and not (context and context.get('no_store_function', False)):
1990 message = self._description + \
1992 self.name_get(cr, user, [id_new], context=context)[0][1] + \
1994 self.log(cr, user, id_new, message, True, context=context)
1995 wf_service = netsvc.LocalService("workflow")
1996 wf_service.trg_create(user, self._name, id_new, cr)
1999 def _where_calc(self, cr, user, args, active_test=True, context=None):
2004 # if the object has a field named 'active', filter out all inactive
2005 # records unless they were explicitely asked for
2006 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2008 active_in_args = False
2010 if a[0] == 'active':
2011 active_in_args = True
2012 if not active_in_args:
2013 args.insert(0, ('active', '=', 1))
2015 args = [('active', '=', 1)]
2018 e = expression.expression(args)
2019 e.parse(cr, user, self, context)
2023 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2027 # implicit filter on current user except for superuser
2031 args.insert(0, ('internal.create_uid', '=', user))
2033 result = self._where_calc(cr, user, args, context=context)
2035 return self.datas.keys()
2039 #Find the value of dict
2042 for id, data in self.datas.items():
2043 counter = counter + 1
2045 if limit and (counter > int(limit)):
2050 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2051 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2052 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2053 elif arg[1] in ['ilike']:
2054 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2064 def unlink(self, cr, uid, ids, context=None):
2066 self._check_access(uid, id, 'unlink')
2067 self.datas.pop(id, None)
2069 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2072 def perm_read(self, cr, user, ids, context=None, details=True):
2074 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2075 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2077 self._check_access(user, id, 'read')
2079 'create_uid': credentials,
2080 'create_date': create_date,
2082 'write_date': False,
2088 def _check_removed_columns(self, cr, log=False):
2089 # nothing to check in memory...
2092 def exists(self, cr, uid, id, context=None):
2093 return id in self.datas
2095 class orm(orm_template):
2096 _sql_constraints = []
2098 _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']
2099 __logger = logging.getLogger('orm')
2100 __schema = logging.getLogger('orm.schema')
2101 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2103 Get the list of records in list view grouped by the given ``groupby`` fields
2105 :param cr: database cursor
2106 :param uid: current user id
2107 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2108 :param fields: list of fields present in the list view specified on the object
2109 :param groupby: list of fields on which to groupby the records
2110 :type fields_list: list (example ['field_name_1', ...])
2111 :param offset: optional number of records to skip
2112 :param limit: optional max number of records to return
2113 :param context: context arguments, like lang, time zone
2114 :param order: optional ``order by`` specification, for overriding the natural
2115 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2116 (supported only for many2one fields currently)
2117 :return: list of dictionaries(one dictionary for each record) containing:
2119 * the values of fields grouped by the fields in ``groupby`` argument
2120 * __domain: list of tuples specifying the search criteria
2121 * __context: dictionary with argument like ``groupby``
2122 :rtype: [{'field_name_1': value, ...]
2123 :raise AccessError: * if user has no read rights on the requested object
2124 * if user tries to bypass access rules for read on the requested object
2127 context = context or {}
2128 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2130 fields = self._columns.keys()
2132 query = self._where_calc(cr, uid, domain, context=context)
2133 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2135 # Take care of adding join(s) if groupby is an '_inherits'ed field
2136 groupby_list = groupby
2137 qualified_groupby_field = groupby
2139 if isinstance(groupby, list):
2140 groupby = groupby[0]
2141 qualified_groupby_field = self._inherits_join_calc(groupby, query)
2144 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?)"
2145 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2146 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"
2148 fget = self.fields_get(cr, uid, fields)
2149 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2151 group_count = group_by = groupby
2153 if fget.get(groupby):
2154 if fget[groupby]['type'] in ('date', 'datetime'):
2155 flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
2156 groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
2158 flist = qualified_groupby_field
2160 # Don't allow arbitrary values, as this would be a SQL injection vector!
2161 raise except_orm(_('Invalid group_by'),
2162 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2165 fields_pre = [f for f in float_int_fields if
2166 f == self.CONCURRENCY_CHECK_FIELD
2167 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2168 for f in fields_pre:
2169 if f not in ['id', 'sequence']:
2170 group_operator = fget[f].get('group_operator', 'sum')
2173 qualified_field = '"%s"."%s"' % (self._table, f)
2174 flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2176 gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2178 from_clause, where_clause, where_clause_params = query.get_sql()
2179 where_clause = where_clause and ' WHERE ' + where_clause
2180 limit_str = limit and ' limit %d' % limit or ''
2181 offset_str = offset and ' offset %d' % offset or ''
2182 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2184 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)
2187 for r in cr.dictfetchall():
2188 for fld, val in r.items():
2189 if val == None: r[fld] = False
2190 alldata[r['id']] = r
2193 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2194 # the IDS of records that have groupby field value = False or '' should be sorted too
2195 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2196 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2197 # 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):
2198 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2202 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2203 if not isinstance(groupby_list, (str, unicode)):
2204 if groupby or not context.get('group_by_no_leaf', False):
2205 d['__context'] = {'group_by': groupby_list[1:]}
2206 if groupby and groupby in fget:
2207 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2208 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2209 days = calendar.monthrange(dt.year, dt.month)[1]
2211 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2212 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),\
2213 (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
2214 del alldata[d['id']][groupby]
2215 d.update(alldata[d['id']])
2219 def _inherits_join_add(self, parent_model_name, query):
2221 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2223 :param parent_model_name: name of the parent model for which the clauses should be added
2224 :param query: query object on which the JOIN should be added
2226 inherits_field = self._inherits[parent_model_name]
2227 parent_model = self.pool.get(parent_model_name)
2228 parent_table_name = parent_model._table
2229 quoted_parent_table_name = '"%s"' % parent_table_name
2230 if quoted_parent_table_name not in query.tables:
2231 query.tables.append(quoted_parent_table_name)
2232 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2234 def _inherits_join_calc(self, field, query):
2236 Adds missing table select and join clause(s) to ``query`` for reaching
2237 the field coming from an '_inherits' parent table (no duplicates).
2239 :param field: name of inherited field to reach
2240 :param query: query object on which the JOIN should be added
2241 :return: qualified name of field, to be used in SELECT clause
2243 current_table = self
2244 while field in current_table._inherit_fields and not field in current_table._columns:
2245 parent_model_name = current_table._inherit_fields[field][0]
2246 parent_table = self.pool.get(parent_model_name)
2247 self._inherits_join_add(parent_model_name, query)
2248 current_table = parent_table
2249 return '"%s".%s' % (current_table._table, field)
2251 def _parent_store_compute(self, cr):
2252 if not self._parent_store:
2254 logger = netsvc.Logger()
2255 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2256 def browse_rec(root, pos=0):
2258 where = self._parent_name+'='+str(root)
2260 where = self._parent_name+' IS NULL'
2261 if self._parent_order:
2262 where += ' order by '+self._parent_order
2263 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2265 for id in cr.fetchall():
2266 pos2 = browse_rec(id[0], pos2)
2267 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2269 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2270 if self._parent_order:
2271 query += ' order by ' + self._parent_order
2274 for (root,) in cr.fetchall():
2275 pos = browse_rec(root, pos)
2278 def _update_store(self, cr, f, k):
2279 logger = netsvc.Logger()
2280 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2281 ss = self._columns[k]._symbol_set
2282 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2283 cr.execute('select id from '+self._table)
2284 ids_lst = map(lambda x: x[0], cr.fetchall())
2287 ids_lst = ids_lst[40:]
2288 res = f.get(cr, self, iids, k, 1, {})
2289 for key, val in res.items():
2292 # if val is a many2one, just write the ID
2293 if type(val) == tuple:
2295 if (val<>False) or (type(val)<>bool):
2296 cr.execute(update_query, (ss[1](val), key))
2298 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2299 """Raise except_orm if value is not among the valid values for the selection field"""
2300 if self._columns[field]._type == 'reference':
2301 val_model, val_id_str = value.split(',', 1)
2304 val_id = long(val_id_str)
2308 raise except_orm(_('ValidateError'),
2309 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2313 if isinstance(self._columns[field].selection, (tuple, list)):
2314 if val in dict(self._columns[field].selection):
2316 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2318 raise except_orm(_('ValidateError'),
2319 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2321 def _check_removed_columns(self, cr, log=False):
2322 # iterate on the database columns to drop the NOT NULL constraints
2323 # of fields which were required but have been removed (or will be added by another module)
2324 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2325 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2326 cr.execute("SELECT a.attname, a.attnotnull"
2327 " FROM pg_class c, pg_attribute a"
2328 " WHERE c.relname=%s"
2329 " AND c.oid=a.attrelid"
2330 " AND a.attisdropped=%s"
2331 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2332 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2334 for column in cr.dictfetchall():
2336 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2337 column['attname'], self._table, self._name)
2338 if column['attnotnull']:
2339 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2340 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2341 self._table, column['attname'])
2343 def _auto_init(self, cr, context=None):
2346 store_compute = False
2349 self._field_create(cr, context=context)
2350 if getattr(self, '_auto', True):
2351 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2353 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2354 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2356 self.__schema.debug("Table '%s': created", self._table)
2359 if self._parent_store:
2360 cr.execute("""SELECT c.relname
2361 FROM pg_class c, pg_attribute a
2362 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2363 """, (self._table, 'parent_left'))
2365 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2366 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2367 if 'parent_left' not in self._columns:
2368 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2370 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2371 self._table, 'parent_left', 'INTEGER')
2372 elif not self._columns['parent_left'].select:
2373 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2375 if 'parent_right' not in self._columns:
2376 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2378 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2379 self._table, 'parent_right', 'INTEGER')
2380 elif not self._columns['parent_right'].select:
2381 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2383 if self._columns[self._parent_name].ondelete != 'cascade':
2384 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2385 self._parent_name, self._name)
2388 store_compute = True
2390 if self._log_access:
2392 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2393 'create_date': 'TIMESTAMP',
2394 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2395 'write_date': 'TIMESTAMP'
2400 FROM pg_class c, pg_attribute a
2401 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2402 """, (self._table, k))
2404 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2406 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2407 self._table, k, logs[k])
2409 self._check_removed_columns(cr, log=False)
2411 # iterate on the "object columns"
2412 todo_update_store = []
2413 update_custom_fields = context.get('update_custom_fields', False)
2415 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 " \
2416 "FROM pg_class c,pg_attribute a,pg_type t " \
2417 "WHERE c.relname=%s " \
2418 "AND c.oid=a.attrelid " \
2419 "AND a.atttypid=t.oid", (self._table,))
2420 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2423 for k in self._columns:
2424 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2426 #Not Updating Custom fields
2427 if k.startswith('x_') and not update_custom_fields:
2430 f = self._columns[k]
2432 if isinstance(f, fields.one2many):
2433 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2435 if self.pool.get(f._obj):
2436 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2437 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2438 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2441 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))
2442 res = cr.fetchone()[0]
2444 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2445 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2446 self._obj, f._fields_id, f._table)
2447 elif isinstance(f, fields.many2many):
2448 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2449 if not cr.dictfetchall():
2450 if not self.pool.get(f._obj):
2451 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2452 ref = self.pool.get(f._obj)._table
2453 # ref = f._obj.replace('.', '_')
2454 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))
2455 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2456 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2457 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2459 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2460 f._rel, self._table, ref)
2462 res = col_data.get(k, [])
2463 res = res and [res] or []
2464 if not res and hasattr(f, 'oldname'):
2465 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 " \
2466 "FROM pg_class c,pg_attribute a,pg_type t " \
2467 "WHERE c.relname=%s " \
2468 "AND a.attname=%s " \
2469 "AND c.oid=a.attrelid " \
2470 "AND a.atttypid=t.oid", (self._table, f.oldname))
2471 res_old = cr.dictfetchall()
2472 if res_old and len(res_old) == 1:
2473 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2475 res[0]['attname'] = k
2476 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2477 self._table, f.oldname, k)
2481 f_pg_type = f_pg_def['typname']
2482 f_pg_size = f_pg_def['size']
2483 f_pg_notnull = f_pg_def['attnotnull']
2484 if isinstance(f, fields.function) and not f.store and\
2485 not getattr(f, 'nodrop', False):
2486 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2487 k, f.string, self._table)
2488 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2490 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2494 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2499 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2500 ('varchar', 'text', 'TEXT', ''),
2501 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2502 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2503 ('timestamp', 'date', 'date', '::date'),
2504 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2505 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2507 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2508 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2509 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2510 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2511 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2513 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2514 self._table, k, f_pg_size, f.size)
2516 if (f_pg_type==c[0]) and (f._type==c[1]):
2517 if f_pg_type != f_obj_type:
2519 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2520 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2521 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2522 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2524 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2525 self._table, k, c[0], c[1])
2528 if f_pg_type != f_obj_type:
2532 newname = k + '_moved' + str(i)
2533 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2534 "WHERE c.relname=%s " \
2535 "AND a.attname=%s " \
2536 "AND c.oid=a.attrelid ", (self._table, newname))
2537 if not cr.fetchone()[0]:
2541 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2542 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2543 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2544 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2545 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2546 self._table, k, f_pg_type, f._type, newname)
2548 # if the field is required and hasn't got a NOT NULL constraint
2549 if f.required and f_pg_notnull == 0:
2550 # set the field to the default value if any
2551 if k in self._defaults:
2552 if callable(self._defaults[k]):
2553 default = self._defaults[k](self, cr, 1, context)
2555 default = self._defaults[k]
2557 if (default is not None):
2558 ss = self._columns[k]._symbol_set
2559 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2560 cr.execute(query, (ss[1](default),))
2561 # add the NOT NULL constraint
2564 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2566 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2569 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2570 "If you want to have it, you should update the records and execute manually:\n"\
2571 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2572 self.__schema.warn(msg, self._table, k, self._table, k)
2574 elif not f.required and f_pg_notnull == 1:
2575 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2577 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2580 indexname = '%s_%s_index' % (self._table, k)
2581 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2582 res2 = cr.dictfetchall()
2583 if not res2 and f.select:
2584 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2586 if f._type == 'text':
2587 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2588 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2589 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2590 " because there is a length limit for indexable btree values!\n"\
2591 "Use a search view instead if you simply want to make the field searchable."
2592 self.__schema.warn(msg, self._table, k, f._type)
2593 if res2 and not f.select:
2594 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2596 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2597 self.__schema.debug(msg, self._table, k, f._type)
2599 if isinstance(f, fields.many2one):
2600 ref = self.pool.get(f._obj)._table
2601 if ref != 'ir_actions':
2602 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2603 'pg_attribute as att1, pg_attribute as att2 '
2604 'WHERE con.conrelid = cl1.oid '
2605 'AND cl1.relname = %s '
2606 'AND con.confrelid = cl2.oid '
2607 'AND cl2.relname = %s '
2608 'AND array_lower(con.conkey, 1) = 1 '
2609 'AND con.conkey[1] = att1.attnum '
2610 'AND att1.attrelid = cl1.oid '
2611 'AND att1.attname = %s '
2612 'AND array_lower(con.confkey, 1) = 1 '
2613 'AND con.confkey[1] = att2.attnum '
2614 'AND att2.attrelid = cl2.oid '
2615 'AND att2.attname = %s '
2616 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2617 res2 = cr.dictfetchall()
2619 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2620 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2621 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2623 self.__schema.debug("Table '%s': column '%s': XXX",
2626 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2628 if not isinstance(f, fields.function) or f.store:
2629 # add the missing field
2630 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2631 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2632 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2633 self._table, k, get_pg_type(f)[1])
2636 if not create and k in self._defaults:
2637 if callable(self._defaults[k]):
2638 default = self._defaults[k](self, cr, 1, context)
2640 default = self._defaults[k]
2642 ss = self._columns[k]._symbol_set
2643 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2644 cr.execute(query, (ss[1](default),))
2646 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2648 if isinstance(f, fields.function):
2650 if f.store is not True:
2651 order = f.store[f.store.keys()[0]][2]
2652 todo_update_store.append((order, f, k))
2654 # and add constraints if needed
2655 if isinstance(f, fields.many2one):
2656 if not self.pool.get(f._obj):
2657 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2658 ref = self.pool.get(f._obj)._table
2659 # ref = f._obj.replace('.', '_')
2660 # ir_actions is inherited so foreign key doesn't work on it
2661 if ref != 'ir_actions':
2662 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2663 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2664 self._table, k, ref, f.ondelete)
2666 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2670 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2671 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2674 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2675 "Try to re-run: openerp-server.py --update=module\n"\
2676 "If it doesn't work, update records and execute manually:\n"\
2677 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2678 self.__logger.warn(msg, k, self._table, self._table, k)
2680 for order, f, k in todo_update_store:
2681 todo_end.append((order, self._update_store, (f, k)))
2684 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2685 create = not bool(cr.fetchone())
2687 cr.commit() # start a new transaction
2689 for (key, con, _) in self._sql_constraints:
2690 conname = '%s_%s' % (self._table, key)
2692 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2693 existing_constraints = cr.dictfetchall()
2698 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2699 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2700 self._table, conname, con),
2701 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2706 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2707 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2708 '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" % (
2714 if not existing_constraints:
2715 # constraint does not exists:
2716 sql_actions['add']['execute'] = True
2717 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2718 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2719 # constraint exists but its definition has changed:
2720 sql_actions['drop']['execute'] = True
2721 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2722 sql_actions['add']['execute'] = True
2723 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2725 # we need to add the constraint:
2726 sql_actions = [item for item in sql_actions.values()]
2727 sql_actions.sort(key=lambda x: x['order'])
2728 for sql_action in [action for action in sql_actions if action['execute']]:
2730 cr.execute(sql_action['query'])
2732 self.__schema.debug(sql_action['msg_ok'])
2734 self.__schema.warn(sql_action['msg_err'])
2738 if hasattr(self, "_sql"):
2739 for line in self._sql.split(';'):
2740 line2 = line.replace('\n', '').strip()
2745 self._parent_store_compute(cr)
2749 def __init__(self, cr):
2750 super(orm, self).__init__(cr)
2752 if not hasattr(self, '_log_access'):
2753 # if not access is not specify, it is the same value as _auto
2754 self._log_access = getattr(self, "_auto", True)
2756 self._columns = self._columns.copy()
2757 for store_field in self._columns:
2758 f = self._columns[store_field]
2759 if hasattr(f, 'digits_change'):
2761 if not isinstance(f, fields.function):
2765 if self._columns[store_field].store is True:
2766 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2768 sm = self._columns[store_field].store
2769 for object, aa in sm.items():
2771 (fnct, fields2, order, length) = aa
2773 (fnct, fields2, order) = aa
2776 raise except_orm('Error',
2777 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2778 self.pool._store_function.setdefault(object, [])
2780 for x, y, z, e, f, l in self.pool._store_function[object]:
2781 if (x==self._name) and (y==store_field) and (e==fields2):
2785 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2786 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2788 for (key, _, msg) in self._sql_constraints:
2789 self.pool._sql_error[self._table+'_'+key] = msg
2791 # Load manual fields
2793 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2795 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2796 for field in cr.dictfetchall():
2797 if field['name'] in self._columns:
2800 'string': field['field_description'],
2801 'required': bool(field['required']),
2802 'readonly': bool(field['readonly']),
2803 'domain': field['domain'] or None,
2804 'size': field['size'],
2805 'ondelete': field['on_delete'],
2806 'translate': (field['translate']),
2807 #'select': int(field['select_level'])
2810 if field['ttype'] == 'selection':
2811 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2812 elif field['ttype'] == 'reference':
2813 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2814 elif field['ttype'] == 'many2one':
2815 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2816 elif field['ttype'] == 'one2many':
2817 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2818 elif field['ttype'] == 'many2many':
2819 _rel1 = field['relation'].replace('.', '_')
2820 _rel2 = field['model'].replace('.', '_')
2821 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2822 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2824 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2825 self._inherits_check()
2826 self._inherits_reload()
2827 if not self._sequence:
2828 self._sequence = self._table + '_id_seq'
2829 for k in self._defaults:
2830 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,)
2831 for f in self._columns:
2832 self._columns[f].restart()
2835 # Update objects that uses this one to update their _inherits fields
2838 def _inherits_reload_src(self):
2839 for obj in self.pool.obj_pool.values():
2840 if self._name in obj._inherits:
2841 obj._inherits_reload()
2843 def _inherits_reload(self):
2845 for table in self._inherits:
2846 res.update(self.pool.get(table)._inherit_fields)
2847 for col in self.pool.get(table)._columns.keys():
2848 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2849 for col in self.pool.get(table)._inherit_fields.keys():
2850 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2851 self._inherit_fields = res
2852 self._inherits_reload_src()
2854 def _inherits_check(self):
2855 for table, field_name in self._inherits.items():
2856 if field_name not in self._columns:
2857 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2858 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2859 required=True, ondelete="cascade")
2860 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2861 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))
2862 self._columns[field_name].required = True
2863 self._columns[field_name].ondelete = "cascade"
2865 #def __getattr__(self, name):
2867 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
2868 # (though inherits doesn't use Python inheritance).
2869 # Handles translating between local ids and remote ids.
2870 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
2871 # when you have inherits.
2873 # for model, field in self._inherits.iteritems():
2874 # proxy = self.pool.get(model)
2875 # if hasattr(proxy, name):
2876 # attribute = getattr(proxy, name)
2877 # if not hasattr(attribute, '__call__'):
2881 # return super(orm, self).__getattr__(name)
2883 # def _proxy(cr, uid, ids, *args, **kwargs):
2884 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
2885 # lst = [obj[field].id for obj in objects if obj[field]]
2886 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
2891 def fields_get(self, cr, user, fields=None, context=None):
2893 Get the description of list of fields
2895 :param cr: database cursor
2896 :param user: current user id
2897 :param fields: list of fields
2898 :param context: context arguments, like lang, time zone
2899 :return: dictionary of field dictionaries, each one describing a field of the business object
2900 :raise AccessError: * if user has no create/write rights on the requested object
2903 ira = self.pool.get('ir.model.access')
2904 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
2905 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
2906 return super(orm, self).fields_get(cr, user, fields, context, write_access)
2908 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
2911 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
2913 fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
2914 if isinstance(ids, (int, long)):
2918 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
2919 result = self._read_flat(cr, user, select, fields, context, load)
2922 for key, v in r.items():
2926 if isinstance(ids, (int, long, dict)):
2927 return result and result[0] or False
2930 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
2935 if fields_to_read == None:
2936 fields_to_read = self._columns.keys()
2938 # Construct a clause for the security rules.
2939 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
2940 # or will at least contain self._table.
2941 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
2943 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
2944 fields_pre = [f for f in fields_to_read if
2945 f == self.CONCURRENCY_CHECK_FIELD
2946 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
2947 ] + self._inherits.values()
2951 def convert_field(f):
2952 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
2953 if f in ('create_date', 'write_date'):
2954 return "date_trunc('second', %s) as %s" % (f_qual, f)
2955 if f == self.CONCURRENCY_CHECK_FIELD:
2956 if self._log_access:
2957 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
2958 return "now()::timestamp AS %s" % (f,)
2959 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2960 return 'length(%s) as "%s"' % (f_qual, f)
2963 fields_pre2 = map(convert_field, fields_pre)
2964 order_by = self._parent_order or self._order
2965 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
2966 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
2968 query += " AND " + (' OR '.join(rule_clause))
2969 query += " ORDER BY " + order_by
2970 for sub_ids in cr.split_for_in_conditions(ids):
2972 cr.execute(query, [tuple(sub_ids)] + rule_params)
2973 if cr.rowcount != len(sub_ids):
2974 raise except_orm(_('AccessError'),
2975 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
2976 % (self._description,))
2978 cr.execute(query, (tuple(sub_ids),))
2979 res.extend(cr.dictfetchall())
2981 res = map(lambda x: {'id': x}, ids)
2983 for f in fields_pre:
2984 if f == self.CONCURRENCY_CHECK_FIELD:
2986 if self._columns[f].translate:
2987 ids = [x['id'] for x in res]
2988 #TODO: optimize out of this loop
2989 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
2991 r[f] = res_trans.get(r['id'], False) or r[f]
2993 for table in self._inherits:
2994 col = self._inherits[table]
2995 cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
2998 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3006 if not record[col]: # if the record is deleted from _inherits table?
3008 record.update(res3[record[col]])
3009 if col not in fields_to_read:
3012 # all fields which need to be post-processed by a simple function (symbol_get)
3013 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3016 for f in fields_post:
3017 r[f] = self._columns[f]._symbol_get(r[f])
3018 ids = [x['id'] for x in res]
3020 # all non inherited fields for which the attribute whose name is in load is False
3021 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3023 # Compute POST fields
3025 for f in fields_post:
3026 todo.setdefault(self._columns[f]._multi, [])
3027 todo[self._columns[f]._multi].append(f)
3028 for key, val in todo.items():
3030 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3033 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3034 multi_fields = res2.get(record['id'],{})
3036 record[pos] = multi_fields.get(pos,[])
3039 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3042 record[f] = res2[record['id']]
3047 for field in vals.copy():
3049 if field in self._columns:
3050 fobj = self._columns[field]
3057 for group in groups:
3058 module = group.split(".")[0]
3059 grp = group.split(".")[1]
3060 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" \
3061 (grp, module, 'res.groups', user))
3062 readonly = cr.fetchall()
3063 if readonly[0][0] >= 1:
3066 elif readonly[0][0] == 0:
3072 if type(vals[field]) == type([]):
3074 elif type(vals[field]) == type(0.0):
3076 elif type(vals[field]) == type(''):
3077 vals[field] = '=No Permission='
3082 def perm_read(self, cr, user, ids, context=None, details=True):
3084 Returns some metadata about the given records.
3086 :param details: if True, \*_uid fields are replaced with the name of the user
3087 :return: list of ownership dictionaries for each requested record
3088 :rtype: list of dictionaries with the following keys:
3091 * create_uid: user who created the record
3092 * create_date: date when the record was created
3093 * write_uid: last user who changed the record
3094 * write_date: date of the last change to the record
3095 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3102 uniq = isinstance(ids, (int, long))
3106 if self._log_access:
3107 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3108 quoted_table = '"%s"' % self._table
3109 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3110 query = '''SELECT %s, __imd.module, __imd.name
3111 FROM %s LEFT JOIN ir_model_data __imd
3112 ON (__imd.model = %%s and __imd.res_id = %s.id)
3113 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3114 cr.execute(query, (self._name, tuple(ids)))
3115 res = cr.dictfetchall()
3118 r[key] = r[key] or False
3119 if details and key in ('write_uid', 'create_uid') and r[key]:
3121 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3123 pass # Leave the numeric uid there
3124 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3125 del r['name'], r['module']
3130 def _check_concurrency(self, cr, ids, context):
3133 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3135 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3136 for sub_ids in cr.split_for_in_conditions(ids):
3139 id_ref = "%s,%s" % (self._name, id)
3140 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3142 ids_to_check.extend([id, update_date])
3143 if not ids_to_check:
3145 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3148 # mention the first one only to keep the error message readable
3149 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3151 def check_access_rule(self, cr, uid, ids, operation, context=None):
3152 """Verifies that the operation given by ``operation`` is allowed for the user
3153 according to ir.rules.
3155 :param operation: one of ``write``, ``unlink``
3156 :raise except_orm: * if current ir.rules do not permit this operation.
3157 :return: None if the operation is allowed
3159 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3161 where_clause = ' and ' + ' and '.join(where_clause)
3162 for sub_ids in cr.split_for_in_conditions(ids):
3163 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3164 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3165 [sub_ids] + where_params)
3166 if cr.rowcount != len(sub_ids):
3167 raise except_orm(_('AccessError'),
3168 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3169 % (operation, self._description))
3171 def unlink(self, cr, uid, ids, context=None):
3173 Delete records with given ids
3175 :param cr: database cursor
3176 :param uid: current user id
3177 :param ids: id or list of ids
3178 :param context: (optional) context arguments, like lang, time zone
3180 :raise AccessError: * if user has no unlink rights on the requested object
3181 * if user tries to bypass access rules for unlink on the requested object
3182 :raise UserError: if the record is default property for other records
3187 if isinstance(ids, (int, long)):
3190 result_store = self._store_get_values(cr, uid, ids, None, context)
3192 self._check_concurrency(cr, ids, context)
3194 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3196 properties = self.pool.get('ir.property')
3197 domain = [('res_id', '=', False),
3198 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3200 if properties.search(cr, uid, domain, context=context):
3201 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3203 wf_service = netsvc.LocalService("workflow")
3205 wf_service.trg_delete(uid, self._name, oid, cr)
3208 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3209 for sub_ids in cr.split_for_in_conditions(ids):
3210 cr.execute('delete from ' + self._table + ' ' \
3211 'where id IN %s', (sub_ids,))
3212 for order, object, store_ids, fields in result_store:
3213 if object != self._name:
3214 obj = self.pool.get(object)
3215 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3216 rids = map(lambda x: x[0], cr.fetchall())
3218 obj._store_set_values(cr, uid, rids, fields, context)
3224 def write(self, cr, user, ids, vals, context=None):
3226 Update records with given ids with the given field values
3228 :param cr: database cursor
3229 :param user: current user id
3231 :param ids: object id or list of object ids to update according to **vals**
3232 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3233 :type vals: dictionary
3234 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3235 :type context: dictionary
3237 :raise AccessError: * if user has no write rights on the requested object
3238 * if user tries to bypass access rules for write on the requested object
3239 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3240 :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)
3242 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3244 + For a many2many field, a list of tuples is expected.
3245 Here is the list of tuple that are accepted, with the corresponding semantics ::
3247 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3248 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3249 (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)
3250 (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)
3251 (4, ID) link to existing record with id = ID (adds a relationship)
3252 (5) unlink all (like using (3,ID) for all linked records)
3253 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3256 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3258 + For a one2many field, a lits of tuples is expected.
3259 Here is the list of tuple that are accepted, with the corresponding semantics ::
3261 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3262 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3263 (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)
3266 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3268 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3269 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3273 for field in vals.copy():
3275 if field in self._columns:
3276 fobj = self._columns[field]
3277 elif field in self._inherit_fields:
3278 fobj = self._inherit_fields[field][2]
3285 for group in groups:
3286 module = group.split(".")[0]
3287 grp = group.split(".")[1]
3288 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", \
3289 (grp, module, 'res.groups', user))
3290 readonly = cr.fetchall()
3291 if readonly[0][0] >= 1:
3294 elif readonly[0][0] == 0:
3306 if isinstance(ids, (int, long)):
3309 self._check_concurrency(cr, ids, context)
3310 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3312 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3314 # No direct update of parent_left/right
3315 vals.pop('parent_left', None)
3316 vals.pop('parent_right', None)
3318 parents_changed = []
3319 if self._parent_store and (self._parent_name in vals):
3320 # The parent_left/right computation may take up to
3321 # 5 seconds. No need to recompute the values if the
3322 # parent is the same. Get the current value of the parent
3323 parent_val = vals[self._parent_name]
3325 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3326 (self._table, self._parent_name, self._parent_name)
3327 cr.execute(query, (tuple(ids), parent_val))
3329 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3330 (self._table, self._parent_name)
3331 cr.execute(query, (tuple(ids),))
3332 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3339 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3341 if field in self._columns:
3342 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3343 if (not totranslate) or not self._columns[field].translate:
3344 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3345 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3346 direct.append(field)
3348 upd_todo.append(field)
3350 updend.append(field)
3351 if field in self._columns \
3352 and hasattr(self._columns[field], 'selection') \
3354 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3356 if self._log_access:
3357 upd0.append('write_uid=%s')
3358 upd0.append('write_date=now()')
3362 self.check_access_rule(cr, user, ids, 'write', context=context)
3363 for sub_ids in cr.split_for_in_conditions(ids):
3364 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3365 'where id IN %s', upd1 + [sub_ids])
3366 if cr.rowcount != len(sub_ids):
3367 raise except_orm(_('AccessError'),
3368 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3373 if self._columns[f].translate:
3374 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3377 # Inserting value to DB
3378 self.write(cr, user, ids, {f: vals[f]})
3379 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3382 # call the 'set' method of fields which are not classic_write
3383 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3385 # default element in context must be removed when call a one2many or many2many
3386 rel_context = context.copy()
3387 for c in context.items():
3388 if c[0].startswith('default_'):
3389 del rel_context[c[0]]
3391 for field in upd_todo:
3393 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3395 for table in self._inherits:
3396 col = self._inherits[table]
3398 for sub_ids in cr.split_for_in_conditions(ids):
3399 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3400 'where id IN %s', (sub_ids,))
3401 nids.extend([x[0] for x in cr.fetchall()])
3405 if self._inherit_fields[val][0] == table:
3408 self.pool.get(table).write(cr, user, nids, v, context)
3410 self._validate(cr, user, ids, context)
3412 # TODO: use _order to set dest at the right position and not first node of parent
3413 # We can't defer parent_store computation because the stored function
3414 # fields that are computer may refer (directly or indirectly) to
3415 # parent_left/right (via a child_of domain)
3418 self.pool._init_parent[self._name] = True
3420 order = self._parent_order or self._order
3421 parent_val = vals[self._parent_name]
3423 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3425 clause, params = '%s IS NULL' % (self._parent_name,), ()
3426 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3427 parents = cr.fetchall()
3429 for id in parents_changed:
3430 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3431 pleft, pright = cr.fetchone()
3432 distance = pright - pleft + 1
3434 # Find Position of the element
3436 for (parent_pright, parent_id) in parents:
3439 position = parent_pright + 1
3441 # It's the first node of the parent
3446 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3447 position = cr.fetchone()[0] + 1
3449 if pleft < position <= pright:
3450 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3452 if pleft < position:
3453 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3454 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3455 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))
3457 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3458 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3459 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))
3461 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3465 for order, object, ids_to_update, fields_to_recompute in result:
3466 key = (object, tuple(fields_to_recompute))
3467 done.setdefault(key, {})
3468 # avoid to do several times the same computation
3470 for id in ids_to_update:
3471 if id not in done[key]:
3472 done[key][id] = True
3474 self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
3476 wf_service = netsvc.LocalService("workflow")
3478 wf_service.trg_write(user, self._name, id, cr)
3482 # TODO: Should set perm to user.xxx
3484 def create(self, cr, user, vals, context=None):
3486 Create new record with specified value
3488 :param cr: database cursor
3489 :param user: current user id
3491 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3492 :type vals: dictionary
3493 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3494 :type context: dictionary
3495 :return: id of new record created
3496 :raise AccessError: * if user has no create rights on the requested object
3497 * if user tries to bypass access rules for create on the requested object
3498 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3499 :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)
3501 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3502 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3508 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3510 vals = self._add_missing_default_values(cr, user, vals, context)
3513 for v in self._inherits:
3514 if self._inherits[v] not in vals:
3517 tocreate[v] = {'id': vals[self._inherits[v]]}
3518 (upd0, upd1, upd2) = ('', '', [])
3520 for v in vals.keys():
3521 if v in self._inherit_fields:
3522 (table, col, col_detail) = self._inherit_fields[v]
3523 tocreate[table][v] = vals[v]
3526 if (v not in self._inherit_fields) and (v not in self._columns):
3529 # Try-except added to filter the creation of those records whose filds are readonly.
3530 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3532 cr.execute("SELECT nextval('"+self._sequence+"')")
3534 raise except_orm(_('UserError'),
3535 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3537 id_new = cr.fetchone()[0]
3538 for table in tocreate:
3539 if self._inherits[table] in vals:
3540 del vals[self._inherits[table]]
3542 record_id = tocreate[table].pop('id', None)
3544 if record_id is None or not record_id:
3545 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3547 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3549 upd0 += ',' + self._inherits[table]
3551 upd2.append(record_id)
3553 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3554 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3556 for bool_field in bool_fields:
3557 if bool_field not in vals:
3558 vals[bool_field] = False
3560 for field in vals.copy():
3562 if field in self._columns:
3563 fobj = self._columns[field]
3565 fobj = self._inherit_fields[field][2]
3571 for group in groups:
3572 module = group.split(".")[0]
3573 grp = group.split(".")[1]
3574 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" % \
3575 (grp, module, 'res.groups', user))
3576 readonly = cr.fetchall()
3577 if readonly[0][0] >= 1:
3580 elif readonly[0][0] == 0:
3588 if self._columns[field]._classic_write:
3589 upd0 = upd0 + ',"' + field + '"'
3590 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3591 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3593 if not isinstance(self._columns[field], fields.related):
3594 upd_todo.append(field)
3595 if field in self._columns \
3596 and hasattr(self._columns[field], 'selection') \
3598 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3599 if self._log_access:
3600 upd0 += ',create_uid,create_date'
3603 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3604 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3605 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3607 if self._parent_store and not context.get('defer_parent_store_computation'):
3609 self.pool._init_parent[self._name] = True
3611 parent = vals.get(self._parent_name, False)
3613 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3615 result_p = cr.fetchall()
3616 for (pleft,) in result_p:
3621 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3622 pleft_old = cr.fetchone()[0]
3625 cr.execute('select max(parent_right) from '+self._table)
3626 pleft = cr.fetchone()[0] or 0
3627 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3628 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3629 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3631 # default element in context must be remove when call a one2many or many2many
3632 rel_context = context.copy()
3633 for c in context.items():
3634 if c[0].startswith('default_'):
3635 del rel_context[c[0]]
3638 for field in upd_todo:
3639 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3640 self._validate(cr, user, [id_new], context)
3642 if not context.get('no_store_function', False):
3643 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3646 for order, object, ids, fields2 in result:
3647 if not (object, ids, fields2) in done:
3648 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3649 done.append((object, ids, fields2))
3651 if self._log_create and not (context and context.get('no_store_function', False)):
3652 message = self._description + \
3654 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3655 "' " + _("created.")
3656 self.log(cr, user, id_new, message, True, context=context)
3657 wf_service = netsvc.LocalService("workflow")
3658 wf_service.trg_create(user, self._name, id_new, cr)
3661 def _store_get_values(self, cr, uid, ids, fields, context):
3662 """Returns an ordered list of fields.functions to call due to
3663 an update operation on ``fields`` of records with ``ids``,
3664 obtained by calling the 'store' functions of these fields,
3665 as setup by their 'store' attribute.
3667 :return: [(priority, model_name, [record_ids,], [function_fields,])]
3669 # FIXME: rewrite, cleanup, use real variable names
3670 # e.g.: http://pastie.org/1222060
3672 fncts = self.pool._store_function.get(self._name, [])
3673 for fnct in range(len(fncts)):
3678 for f in (fields or []):
3679 if f in fncts[fnct][3]:
3685 result.setdefault(fncts[fnct][0], {})
3687 # uid == 1 for accessing objects having rules defined on store fields
3688 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3689 for id in filter(None, ids2):
3690 result[fncts[fnct][0]].setdefault(id, [])
3691 result[fncts[fnct][0]][id].append(fnct)
3693 for object in result:
3695 for id, fnct in result[object].items():
3696 k2.setdefault(tuple(fnct), [])
3697 k2[tuple(fnct)].append(id)
3698 for fnct, id in k2.items():
3699 dict.setdefault(fncts[fnct[0]][4], [])
3700 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3708 def _store_set_values(self, cr, uid, ids, fields, context):
3709 """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
3710 respecting ``multi`` attributes), and stores the resulting values in the database directly."""
3715 if self._log_access:
3716 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3720 field_dict.setdefault(r[0], [])
3721 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3722 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3723 for i in self.pool._store_function.get(self._name, []):
3725 up_write_date = write_date + datetime.timedelta(hours=i[5])
3726 if datetime.datetime.now() < up_write_date:
3728 field_dict[r[0]].append(i[1])
3734 if self._columns[f]._multi not in keys:
3735 keys.append(self._columns[f]._multi)
3736 todo.setdefault(self._columns[f]._multi, [])
3737 todo[self._columns[f]._multi].append(f)
3741 # uid == 1 for accessing objects having rules defined on store fields
3742 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3743 for id, value in result.items():
3745 for f in value.keys():
3746 if f in field_dict[id]:
3753 if self._columns[v]._type in ('many2one', 'one2one'):
3755 value[v] = value[v][0]
3758 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3759 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3762 cr.execute('update "' + self._table + '" set ' + \
3763 ','.join(upd0) + ' where id = %s', upd1)
3767 # uid == 1 for accessing objects having rules defined on store fields
3768 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3769 for r in result.keys():
3771 if r in field_dict.keys():
3772 if f in field_dict[r]:
3774 for id, value in result.items():
3775 if self._columns[f]._type in ('many2one', 'one2one'):
3780 cr.execute('update "' + self._table + '" set ' + \
3781 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3787 def perm_write(self, cr, user, ids, fields, context=None):
3788 raise NotImplementedError(_('This method does not exist anymore'))
3790 # TODO: ameliorer avec NULL
3791 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3792 """Computes the WHERE clause needed to implement an OpenERP domain.
3793 :param domain: the domain to compute
3795 :param active_test: whether the default filtering of records with ``active``
3796 field set to ``False`` should be applied.
3797 :return: the query expressing the given domain as provided in domain
3798 :rtype: osv.query.Query
3803 # if the object has a field named 'active', filter out all inactive
3804 # records unless they were explicitely asked for
3805 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3807 active_in_args = False
3809 if a[0] == 'active':
3810 active_in_args = True
3811 if not active_in_args:
3812 domain.insert(0, ('active', '=', 1))
3814 domain = [('active', '=', 1)]
3818 e = expression.expression(domain)
3819 e.parse(cr, user, self, context)
3820 tables = e.get_tables()
3821 where_clause, where_params = e.to_sql()
3822 where_clause = where_clause and [where_clause] or []
3824 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3826 return Query(tables, where_clause, where_params)
3828 def _check_qorder(self, word):
3829 if not regex_order.match(word):
3830 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)'))
3833 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3834 """Add what's missing in ``query`` to implement all appropriate ir.rules
3835 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3837 :param query: the current query object
3839 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3841 if parent_model and child_object:
3842 # as inherited rules are being applied, we need to add the missing JOIN
3843 # to reach the parent table (if it was not JOINed yet in the query)
3844 child_object._inherits_join_add(parent_model, query)
3845 query.where_clause += added_clause
3846 query.where_clause_params += added_params
3847 for table in added_tables:
3848 if table not in query.tables:
3849 query.tables.append(table)
3853 # apply main rules on the object
3854 rule_obj = self.pool.get('ir.rule')
3855 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3857 # apply ir.rules from the parents (through _inherits)
3858 for inherited_model in self._inherits:
3859 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3860 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3862 def _generate_m2o_order_by(self, order_field, query):
3864 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3865 either native m2o fields or function/related fields that are stored, including
3866 intermediate JOINs for inheritance if required.
3868 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3870 if order_field not in self._columns and order_field in self._inherit_fields:
3871 # also add missing joins for reaching the table containing the m2o field
3872 qualified_field = self._inherits_join_calc(order_field, query)
3873 order_field_column = self._inherit_fields[order_field][2]
3875 qualified_field = '"%s"."%s"' % (self._table, order_field)
3876 order_field_column = self._columns[order_field]
3878 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3879 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
3880 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
3881 "to be used as ordering fields! Ignoring sorting for %s.%s",
3882 self._name, order_field)
3885 # figure out the applicable order_by for the m2o
3886 dest_model = self.pool.get(order_field_column._obj)
3887 m2o_order = dest_model._order
3888 if not regex_order.match(m2o_order):
3889 # _order is complex, can't use it here, so we default to _rec_name
3890 m2o_order = dest_model._rec_name
3892 # extract the field names, to be able to qualify them and add desc/asc
3894 for order_part in m2o_order.split(","):
3895 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
3896 m2o_order = m2o_order_list
3898 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
3899 # as we don't want to exclude results that have NULL values for the m2o
3900 src_table, src_field = qualified_field.replace('"','').split('.', 1)
3901 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
3902 qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
3903 return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
3906 def _generate_order_by(self, order_spec, query):
3908 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
3909 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
3911 :raise" except_orm in case order_spec is malformed
3913 order_by_clause = self._order
3915 order_by_elements = []
3916 self._check_qorder(order_spec)
3917 for order_part in order_spec.split(','):
3918 order_split = order_part.strip().split(' ')
3919 order_field = order_split[0].strip()
3920 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
3922 if order_field == 'id':
3923 order_by_clause = '"%s"."%s"' % (self._table, order_field)
3924 elif order_field in self._columns:
3925 order_column = self._columns[order_field]
3926 if order_column._classic_read:
3927 inner_clause = '"%s"."%s"' % (self._table, order_field)
3928 elif order_column._type == 'many2one':
3929 inner_clause = self._generate_m2o_order_by(order_field, query)
3931 continue # ignore non-readable or "non-joinable" fields
3932 elif order_field in self._inherit_fields:
3933 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
3934 order_column = parent_obj._columns[order_field]
3935 if order_column._classic_read:
3936 inner_clause = self._inherits_join_calc(order_field, query)
3937 elif order_column._type == 'many2one':
3938 inner_clause = self._generate_m2o_order_by(order_field, query)
3940 continue # ignore non-readable or "non-joinable" fields
3942 if isinstance(inner_clause, list):
3943 for clause in inner_clause:
3944 order_by_elements.append("%s %s" % (clause, order_direction))
3946 order_by_elements.append("%s %s" % (inner_clause, order_direction))
3947 if order_by_elements:
3948 order_by_clause = ",".join(order_by_elements)
3950 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
3952 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
3954 Private implementation of search() method, allowing specifying the uid to use for the access right check.
3955 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
3956 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
3957 This is ok at the security level because this method is private and not callable through XML-RPC.
3959 :param access_rights_uid: optional user ID to use when checking access rights
3960 (not for ir.rules, this is only for ir.model.access)
3964 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
3966 query = self._where_calc(cr, user, args, context=context)
3967 self._apply_ir_rules(cr, user, query, 'read', context=context)
3968 order_by = self._generate_order_by(order, query)
3969 from_clause, where_clause, where_clause_params = query.get_sql()
3971 limit_str = limit and ' limit %d' % limit or ''
3972 offset_str = offset and ' offset %d' % offset or ''
3973 where_str = where_clause and (" WHERE %s" % where_clause) or ''
3976 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
3979 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
3981 return [x[0] for x in res]
3983 # returns the different values ever entered for one field
3984 # this is used, for example, in the client when the user hits enter on
3986 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
3989 if field in self._inherit_fields:
3990 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
3992 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
3994 def copy_data(self, cr, uid, id, default=None, context=None):
3996 Copy given record's data with all its fields values
3998 :param cr: database cursor
3999 :param user: current user id
4000 :param id: id of the record to copy
4001 :param default: field values to override in the original values of the copied record
4002 :type default: dictionary
4003 :param context: context arguments, like lang, time zone
4004 :type context: dictionary
4005 :return: dictionary containing all the field values
4011 # avoid recursion through already copied records in case of circular relationship
4012 seen_map = context.setdefault('__copy_data_seen',{})
4013 if id in seen_map.setdefault(self._name,[]):
4015 seen_map[self._name].append(id)
4019 if 'state' not in default:
4020 if 'state' in self._defaults:
4021 if callable(self._defaults['state']):
4022 default['state'] = self._defaults['state'](self, cr, uid, context)
4024 default['state'] = self._defaults['state']
4026 context_wo_lang = context.copy()
4027 if 'lang' in context:
4028 del context_wo_lang['lang']
4029 data = self.read(cr, uid, [id,], context=context_wo_lang)
4033 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4035 fields = self.fields_get(cr, uid, context=context)
4037 ftype = fields[f]['type']
4039 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4043 data[f] = default[f]
4044 elif 'function' in fields[f]:
4046 elif ftype == 'many2one':
4048 data[f] = data[f] and data[f][0]
4051 elif ftype in ('one2many', 'one2one'):
4053 rel = self.pool.get(fields[f]['relation'])
4055 # duplicate following the order of the ids
4056 # because we'll rely on it later for copying
4057 # translations in copy_translation()!
4059 for rel_id in data[f]:
4060 # the lines are first duplicated using the wrong (old)
4061 # parent but then are reassigned to the correct one thanks
4062 # to the (0, 0, ...)
4063 d = rel.copy_data(cr, uid, rel_id, context=context)
4065 res.append((0, 0, d))
4067 elif ftype == 'many2many':
4068 data[f] = [(6, 0, data[f])]
4072 # make sure we don't break the current parent_store structure and
4073 # force a clean recompute!
4074 for parent_column in ['parent_left', 'parent_right']:
4075 data.pop(parent_column, None)
4077 for v in self._inherits:
4078 del data[self._inherits[v]]
4081 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4085 # avoid recursion through already copied records in case of circular relationship
4086 seen_map = context.setdefault('__copy_translations_seen',{})
4087 if old_id in seen_map.setdefault(self._name,[]):
4089 seen_map[self._name].append(old_id)
4091 trans_obj = self.pool.get('ir.translation')
4092 fields = self.fields_get(cr, uid, context=context)
4094 translation_records = []
4095 for field_name, field_def in fields.items():
4096 # we must recursively copy the translations for o2o and o2m
4097 if field_def['type'] in ('one2one', 'one2many'):
4098 target_obj = self.pool.get(field_def['relation'])
4099 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4100 # here we rely on the order of the ids to match the translations
4101 # as foreseen in copy_data()
4102 old_children = sorted(old_record[field_name])
4103 new_children = sorted(new_record[field_name])
4104 for (old_child, new_child) in zip(old_children, new_children):
4105 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4106 # and for translatable fields we keep them for copy
4107 elif field_def.get('translate'):
4109 if field_name in self._columns:
4110 trans_name = self._name + "," + field_name
4111 elif field_name in self._inherit_fields:
4112 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4114 trans_ids = trans_obj.search(cr, uid, [
4115 ('name', '=', trans_name),
4116 ('res_id', '=', old_id)
4118 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4120 for record in translation_records:
4122 record['res_id'] = new_id
4123 trans_obj.create(cr, uid, record, context=context)
4126 def copy(self, cr, uid, id, default=None, context=None):
4128 Duplicate record with given id updating it with default values
4130 :param cr: database cursor
4131 :param uid: current user id
4132 :param id: id of the record to copy
4133 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4134 :type default: dictionary
4135 :param context: context arguments, like lang, time zone
4136 :type context: dictionary
4142 context = context.copy()
4143 data = self.copy_data(cr, uid, id, default, context)
4144 new_id = self.create(cr, uid, data, context)
4145 self.copy_translations(cr, uid, id, new_id, context)
4148 def exists(self, cr, uid, ids, context=None):
4149 if type(ids) in (int, long):
4151 query = 'SELECT count(1) FROM "%s"' % (self._table)
4152 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4153 return cr.fetchone()[0] == len(ids)
4155 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4156 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4157 self._name, DeprecationWarning, stacklevel=3)
4158 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4159 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4160 return self._check_recursion(cr, uid, ids, context, parent)
4162 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4164 Verifies that there is no loop in a hierarchical structure of records,
4165 by following the parent relationship using the **parent** field until a loop
4166 is detected or until a top-level record is found.
4168 :param cr: database cursor
4169 :param uid: current user id
4170 :param ids: list of ids of records to check
4171 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4172 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4176 parent = self._parent_name
4178 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4181 for i in range(0, len(ids), cr.IN_MAX):
4182 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4183 cr.execute(query, (tuple(sub_ids_parent),))
4184 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4185 ids_parent = ids_parent2
4186 for i in ids_parent:
4191 def get_xml_ids(self, cr, uid, ids, *args, **kwargs):
4192 """Find out the XML ID(s) of any database record.
4194 **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
4196 :return: map of ids to the list of their fully qualified XML IDs
4197 (empty list when there's none).
4199 model_data_obj = self.pool.get('ir.model.data')
4200 data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
4201 data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
4204 # can't use dict.fromkeys() as the list would be shared!
4206 for record in data_results:
4207 result[record['res_id']].append('%(module)s.%(name)s' % record)
4210 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4211 """Find out the XML ID of any database record, if there
4212 is one. This method works as a possible implementation
4213 for a function field, to be able to add it to any
4214 model object easily, referencing it as ``osv.osv.get_xml_id``.
4216 When multiple XML IDs exist for a record, only one
4217 of them is returned (randomly).
4219 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4221 :return: map of ids to their fully qualified XML ID,
4222 defaulting to an empty string when there's none
4223 (to be usable as a function field).
4225 results = self.get_xml_ids(cr, uid, ids)
4226 for k, v in results.items():
4233 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: