1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 # Object relationnal mapping to postgresql module
24 # . Hierarchical structure
25 # . Constraints consistency, validations
26 # . Object meta Data depends on its status
27 # . Optimised processing by complex query (multiple actions at once)
28 # . Default fields value
29 # . Permissions optimisation
30 # . Persistant object: DB postgresql
32 # . Multi-level caching system
33 # . 2 different inheritancies
35 # - classicals (varchar, integer, boolean, ...)
36 # - relations (one2many, many2one, many2many)
53 from lxml import etree
54 from tools.config import config
55 from tools.translate import _
58 from query import Query
60 from tools.safe_eval import safe_eval as eval
62 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
65 POSTGRES_CONFDELTYPES = {
73 # List of etree._Element subclasses that we choose to ignore when parsing view architecture.
74 # We include the *Base ones just in case, currently they seem to be subclasses of the _* ones.
75 SKIPPED_ELEMENT_TYPES = (etree._Comment, etree._ProcessingInstruction, etree.CommentBase, etree.PIBase)
77 def last_day_of_current_month():
78 today = datetime.date.today()
79 last_day = str(calendar.monthrange(today.year, today.month)[1])
80 return time.strftime('%Y-%m-' + last_day)
82 def intersect(la, lb):
83 return filter(lambda x: x in lb, la)
85 class except_orm(Exception):
86 def __init__(self, name, value):
89 self.args = (name, value)
91 class BrowseRecordError(Exception):
94 # Readonly python database object browser
95 class browse_null(object):
100 def __getitem__(self, name):
103 def __getattr__(self, name):
104 return None # XXX: return self ?
112 def __nonzero__(self):
115 def __unicode__(self):
120 # TODO: execute an object method on browse_record_list
122 class browse_record_list(list):
124 def __init__(self, lst, context=None):
127 super(browse_record_list, self).__init__(lst)
128 self.context = context
131 class browse_record(object):
132 logger = netsvc.Logger()
134 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
136 table : the object (inherited from orm)
137 context : dictionary with an optional context
139 if fields_process is None:
143 self._list_class = list_class or browse_record_list
148 self._table_name = self._table._name
149 self.__logger = logging.getLogger(
150 'osv.browse_record.' + self._table_name)
151 self._context = context
152 self._fields_process = fields_process
154 cache.setdefault(table._name, {})
155 self._data = cache[table._name]
157 if not (id and isinstance(id, (int, long,))):
158 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
159 # if not table.exists(cr, uid, id, context):
160 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
162 if id not in self._data:
163 self._data[id] = {'id': id}
167 def __getitem__(self, name):
171 if name not in self._data[self._id]:
172 # build the list of fields we will fetch
174 # fetch the definition of the field which was asked for
175 if name in self._table._columns:
176 col = self._table._columns[name]
177 elif name in self._table._inherit_fields:
178 col = self._table._inherit_fields[name][2]
179 elif hasattr(self._table, str(name)):
180 attr = getattr(self._table, name)
182 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
183 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
187 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
188 "Field '%s' does not exist in object '%s': \n%s" % (
189 name, self, ''.join(traceback.format_exc())))
190 raise KeyError("Field '%s' does not exist in object '%s'" % (
193 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
195 # gen the list of "local" (ie not inherited) fields which are classic or many2one
196 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
197 # gen the list of inherited fields
198 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
199 # complete the field list with the inherited fields which are classic or many2one
200 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
201 # otherwise we fetch only that field
203 fields_to_fetch = [(name, col)]
204 ids = filter(lambda id: name not in self._data[id], self._data.keys())
206 field_names = map(lambda x: x[0], fields_to_fetch)
207 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
208 if self._fields_process:
209 lang = self._context.get('lang', 'en_US') or 'en_US'
210 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
212 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
213 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
215 for field_name, field_column in fields_to_fetch:
216 if field_column._type in self._fields_process:
217 for result_line in field_values:
218 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
219 if result_line[field_name]:
220 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
223 # Where did those ids come from? Perhaps old entries in ir_model_dat?
224 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
225 raise KeyError('Field %s not found in %s'%(name, self))
226 # create browse records for 'remote' objects
227 for result_line in field_values:
229 for field_name, field_column in fields_to_fetch:
230 if field_column._type in ('many2one', 'one2one'):
231 if result_line[field_name]:
232 obj = self._table.pool.get(field_column._obj)
233 if isinstance(result_line[field_name], (list, tuple)):
234 value = result_line[field_name][0]
236 value = result_line[field_name]
238 # FIXME: this happen when a _inherits object
239 # overwrite a field of it parent. Need
240 # testing to be sure we got the right
241 # object and not the parent one.
242 if not isinstance(value, browse_record):
243 new_data[field_name] = browse_record(self._cr,
244 self._uid, value, obj, self._cache,
245 context=self._context,
246 list_class=self._list_class,
247 fields_process=self._fields_process)
249 new_data[field_name] = value
251 new_data[field_name] = browse_null()
253 new_data[field_name] = browse_null()
254 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
255 new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(field_column._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
256 elif field_column._type in ('reference'):
257 if result_line[field_name]:
258 if isinstance(result_line[field_name], browse_record):
259 new_data[field_name] = result_line[field_name]
261 ref_obj, ref_id = result_line[field_name].split(',')
262 ref_id = long(ref_id)
264 obj = self._table.pool.get(ref_obj)
265 new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
267 new_data[field_name] = browse_null()
269 new_data[field_name] = browse_null()
271 new_data[field_name] = result_line[field_name]
272 self._data[result_line['id']].update(new_data)
274 if not name in self._data[self._id]:
275 #how did this happen?
276 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
277 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
278 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
279 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
280 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
281 return self._data[self._id][name]
283 def __getattr__(self, name):
287 raise AttributeError(e)
289 def __contains__(self, name):
290 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
292 def __hasattr__(self, name):
299 return "browse_record(%s, %d)" % (self._table_name, self._id)
301 def __eq__(self, other):
302 if not isinstance(other, browse_record):
304 return (self._table_name, self._id) == (other._table_name, other._id)
306 def __ne__(self, other):
307 if not isinstance(other, browse_record):
309 return (self._table_name, self._id) != (other._table_name, other._id)
311 # we need to define __unicode__ even though we've already defined __str__
312 # because we have overridden __getattr__
313 def __unicode__(self):
314 return unicode(str(self))
317 return hash((self._table_name, self._id))
325 (type returned by postgres when the column was created, type expression to create the column)
329 fields.boolean: 'bool',
330 fields.integer: 'int4',
331 fields.integer_big: 'int8',
335 fields.datetime: 'timestamp',
336 fields.binary: 'bytea',
337 fields.many2one: 'int4',
339 if type(f) in type_dict:
340 f_type = (type_dict[type(f)], type_dict[type(f)])
341 elif isinstance(f, fields.float):
343 f_type = ('numeric', 'NUMERIC')
345 f_type = ('float8', 'DOUBLE PRECISION')
346 elif isinstance(f, (fields.char, fields.reference)):
347 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
348 elif isinstance(f, fields.selection):
349 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
350 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
351 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
354 f_size = getattr(f, 'size', None) or 16
357 f_type = ('int4', 'INTEGER')
359 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
360 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
361 t = eval('fields.'+(f._type), globals())
362 f_type = (type_dict[t], type_dict[t])
363 elif isinstance(f, fields.function) and f._type == 'float':
365 f_type = ('numeric', 'NUMERIC')
367 f_type = ('float8', 'DOUBLE PRECISION')
368 elif isinstance(f, fields.function) and f._type == 'selection':
369 f_type = ('text', 'text')
370 elif isinstance(f, fields.function) and f._type == 'char':
371 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
373 logger = netsvc.Logger()
374 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
379 class orm_template(object):
385 _parent_name = 'parent_id'
386 _parent_store = False
387 _parent_order = False
397 CONCURRENCY_CHECK_FIELD = '__last_update'
398 def log(self, cr, uid, id, message, secondary=False, context=None):
399 return self.pool.get('res.log').create(cr, uid,
402 'res_model': self._name,
403 'secondary': secondary,
409 def view_init(self, cr, uid, fields_list, context=None):
410 """Override this method to do specific things when a view on the object is opened."""
413 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
414 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
416 def _field_create(self, cr, context=None):
419 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
421 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
422 model_id = cr.fetchone()[0]
423 cr.execute("INSERT INTO ir_model (id,model, name, info,state) VALUES (%s, %s, %s, %s, %s)", (model_id, self._name, self._description, self.__doc__, 'base'))
425 model_id = cr.fetchone()[0]
426 if 'module' in context:
427 name_id = 'model_'+self._name.replace('.', '_')
428 cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id, model_id, context['module']))
430 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
431 (name_id, context['module'], 'ir.model', model_id)
436 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
438 for rec in cr.dictfetchall():
439 cols[rec['name']] = rec
441 for (k, f) in self._columns.items():
443 'model_id': model_id,
446 'field_description': f.string.replace("'", " "),
448 'relation': f._obj or '',
449 'view_load': (f.view_load and 1) or 0,
450 'select_level': tools.ustr(f.select or 0),
451 'readonly': (f.readonly and 1) or 0,
452 'required': (f.required and 1) or 0,
453 'selectable': (f.selectable and 1) or 0,
454 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
456 # When its a custom field,it does not contain f.select
457 if context.get('field_state', 'base') == 'manual':
458 if context.get('field_name', '') == k:
459 vals['select_level'] = context.get('select', '0')
460 #setting value to let the problem NOT occur next time
462 vals['select_level'] = cols[k]['select_level']
465 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
466 id = cr.fetchone()[0]
468 cr.execute("""INSERT INTO ir_model_fields (
469 id, model_id, model, name, field_description, ttype,
470 relation,view_load,state,select_level,relation_field
472 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
474 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
475 vals['relation'], bool(vals['view_load']), 'base',
476 vals['select_level'], vals['relation_field']
478 if 'module' in context:
479 name1 = 'field_' + self._table + '_' + k
480 cr.execute("select name from ir_model_data where name=%s", (name1,))
482 name1 = name1 + "_" + str(id)
483 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
484 (name1, context['module'], 'ir.model.fields', id)
487 for key, val in vals.items():
488 if cols[k][key] != vals[key]:
489 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
491 cr.execute("""UPDATE ir_model_fields SET
492 model_id=%s, field_description=%s, ttype=%s, relation=%s,
493 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
495 model=%s AND name=%s""", (
496 vals['model_id'], vals['field_description'], vals['ttype'],
497 vals['relation'], bool(vals['view_load']),
498 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
503 def _auto_init(self, cr, context=None):
504 self._field_create(cr, context=context)
506 def __init__(self, cr):
507 if not self._name and not hasattr(self, '_inherit'):
508 name = type(self).__name__.split('.')[0]
509 msg = "The class %s has to have a _name attribute" % name
511 logger = netsvc.Logger()
512 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
513 raise except_orm('ValueError', msg)
515 if not self._description:
516 self._description = self._name
518 self._table = self._name.replace('.', '_')
520 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
522 Fetch records as objects allowing to use dot notation to browse fields and relations
524 :param cr: database cursor
525 :param user: current user id
526 :param select: id or list of ids
527 :param context: context arguments, like lang, time zone
528 :rtype: object or list of objects requested
531 self._list_class = list_class or browse_record_list
533 # need to accepts ints and longs because ids coming from a method
534 # launched by button in the interface have a type long...
535 if isinstance(select, (int, long)):
536 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
537 elif isinstance(select, list):
538 return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context=context)
542 def __export_row(self, cr, uid, row, fields, context=None):
546 def check_type(field_type):
547 if field_type == 'float':
549 elif field_type == 'integer':
551 elif field_type == 'boolean':
555 def selection_field(in_field):
556 col_obj = self.pool.get(in_field.keys()[0])
557 if f[i] in col_obj._columns.keys():
558 return col_obj._columns[f[i]]
559 elif f[i] in col_obj._inherits.keys():
560 selection_field(col_obj._inherits)
565 data = map(lambda x: '', range(len(fields)))
567 for fpos in range(len(fields)):
576 model_data = self.pool.get('ir.model.data')
577 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
579 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
581 r = '%s.%s' % (d['module'], d['name'])
588 # To display external name of selection field when its exported
590 if f[i] in self._columns.keys():
591 cols = self._columns[f[i]]
592 elif f[i] in self._inherit_fields.keys():
593 cols = selection_field(self._inherits)
594 if cols and cols._type == 'selection':
595 sel_list = cols.selection
596 if r and type(sel_list) == type([]):
597 r = [x[1] for x in sel_list if r==x[0]]
598 r = r and r[0] or False
600 if f[i] in self._columns:
601 r = check_type(self._columns[f[i]]._type)
602 elif f[i] in self._inherit_fields:
603 r = check_type(self._inherit_fields[f[i]][2]._type)
606 if isinstance(r, (browse_record_list, list)):
608 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
611 if [x for x in fields2 if x]:
615 lines2 = self.__export_row(cr, uid, row2, fields2,
618 for fpos2 in range(len(fields)):
619 if lines2 and lines2[0][fpos2]:
620 data[fpos2] = lines2[0][fpos2]
624 if isinstance(rr.name, browse_record):
626 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
627 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
628 dt += tools.ustr(rr_name or '') + ','
638 if isinstance(r, browse_record):
639 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
640 r = r and r[0] and r[0][1] or ''
641 data[fpos] = tools.ustr(r or '')
642 return [data] + lines
644 def export_data(self, cr, uid, ids, fields_to_export, context=None):
646 Export fields for selected objects
648 :param cr: database cursor
649 :param uid: current user id
650 :param ids: list of ids
651 :param fields_to_export: list of fields
652 :param context: context arguments, like lang, time zone
653 :rtype: dictionary with a *datas* matrix
655 This method is used when exporting data via client menu
660 cols = self._columns.copy()
661 for f in self._inherit_fields:
662 cols.update({f: self._inherit_fields[f][2]})
664 if x=='.id': return [x]
665 return x.replace(':id','/id').replace('.id','/.id').split('/')
666 fields_to_export = map(fsplit, fields_to_export)
667 fields_export = fields_to_export + []
671 for row in self.browse(cr, uid, ids, context):
672 datas += self.__export_row(cr, uid, row, fields_to_export, context)
673 return {'datas': datas}
675 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
677 Import given data in given module
679 :param cr: database cursor
680 :param uid: current user id
681 :param fields: list of fields
682 :param data: data to import
683 :param mode: 'init' or 'update' for record creation
684 :param current_module: module name
685 :param noupdate: flag for record creation
686 :param context: context arguments, like lang, time zone,
687 :param filename: optional file to store partial import state for recovery
690 This method is used when importing data via client menu
695 fields = map(lambda x: x.split('/'), fields)
696 logger = netsvc.Logger()
697 ir_model_data_obj = self.pool.get('ir.model.data')
704 def _check_db_id(self, model_name, db_id):
705 obj_model = self.pool.get(model_name)
706 ids = obj_model.search(cr, uid, [('id', '=', int(db_id))])
708 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, db_id))
711 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0):
712 line = datas[position]
721 ir_model_data_obj = self.pool.get('ir.model.data')
723 # Import normal fields
725 for i in range(len(fields)):
727 raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
732 if prefix and not prefix[0] in field:
735 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith('.id') and (field[len(prefix)]<>'.id'):
739 field_name = field[0].split('.')[0]
740 model_rel = fields_def[field_name]['relation']
742 if fields_def[field[len(prefix)][:-3]]['type'] == 'many2many':
744 for db_id in line[i].split(config.get('csv_internal_sep')):
746 _check_db_id(self, model_rel, db_id)
749 warning += [tools.exception_to_unicode(e)]
750 logger.notifyChannel("import", netsvc.LOG_ERROR,
751 tools.exception_to_unicode(e))
753 res = [(6, 0, res_id)]
756 _check_db_id(self, model_rel, line[i])
759 warning += [tools.exception_to_unicode(e)]
760 logger.notifyChannel("import", netsvc.LOG_ERROR,
761 tools.exception_to_unicode(e))
762 row[field_name] = res or False
765 if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
768 if fields_def[field[len(prefix)][:-3]]['type'] == 'many2many':
770 for word in line[i].split(config.get('csv_internal_sep')):
772 module, xml_id = word.rsplit('.', 1)
774 module, xml_id = current_module, word
775 id = ir_model_data_obj._get_id(cr, uid, module,
777 res_id2 = ir_model_data_obj.read(cr, uid, [id],
778 ['res_id'])[0]['res_id']
780 res_id.append(res_id2)
782 res_id = [(6, 0, res_id)]
785 module, xml_id = line[i].rsplit('.', 1)
787 module, xml_id = current_module, line[i]
788 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
789 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
791 res_id = ir_model_data[0]['res_id']
793 raise ValueError('No references to %s.%s' % (module, xml_id))
794 row[field[-1][:-3]] = res_id or False
796 if (len(field) == len(prefix)+1) and \
797 len(field[len(prefix)].split(':lang=')) == 2:
798 f, lang = field[len(prefix)].split(':lang=')
799 translate.setdefault(lang, {})[f] = line[i] or False
801 if (len(field) == len(prefix)+1) and \
802 (prefix == field[0:len(prefix)]):
803 if field[len(prefix)] == "id":
806 is_xml_id = data_id = line[i]
807 d = data_id.split('.')
808 module = len(d) > 1 and d[0] or ''
809 name = len(d) > 1 and d[1] or d[0]
810 data_ids = ir_model_data_obj.search(cr, uid, [('module', '=', module), ('model', '=', model_name), ('name', '=', name)])
812 d = ir_model_data_obj.read(cr, uid, data_ids, ['res_id'])[0]
814 if is_db_id and not db_id:
815 data_ids = ir_model_data_obj.search(cr, uid, [('module', '=', module), ('model', '=', model_name), ('res_id', '=', is_db_id)])
816 if not len(data_ids):
817 ir_model_data_obj.create(cr, uid, {'module': module, 'model': model_name, 'name': name, 'res_id': is_db_id})
819 if is_db_id and int(db_id) != int(is_db_id):
820 warning += [_("Id is not the same than existing one: %s") % (is_db_id)]
821 logger.notifyChannel("import", netsvc.LOG_ERROR,
822 _("Id is not the same than existing one: %s") % (is_db_id))
826 if field[len(prefix)] == ".id":
829 _check_db_id(self, model_name, line[i])
830 data_res_id = is_db_id = int(line[i])
832 warning += [tools.exception_to_unicode(e)]
833 logger.notifyChannel("import", netsvc.LOG_ERROR,
834 tools.exception_to_unicode(e))
836 data_ids = ir_model_data_obj.search(cr, uid, [('model', '=', model_name), ('res_id', '=', line[i])])
838 d = ir_model_data_obj.read(cr, uid, data_ids, ['name', 'module'])[0]
841 data_id = '%s.%s' % (d['module'], d['name'])
844 if is_xml_id and not data_id:
846 if is_xml_id and is_xml_id != data_id:
847 warning += [_("Id is not the same than existing one: %s") % (line[i])]
848 logger.notifyChannel("import", netsvc.LOG_ERROR,
849 _("Id is not the same than existing one: %s") % (line[i]))
852 if fields_def[field[len(prefix)]]['type'] == 'integer':
853 res = line[i] and int(line[i])
854 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
855 res = line[i].lower() not in ('0', 'false', 'off')
856 elif fields_def[field[len(prefix)]]['type'] == 'float':
857 res = line[i] and float(line[i])
858 elif fields_def[field[len(prefix)]]['type'] == 'selection':
860 if isinstance(fields_def[field[len(prefix)]]['selection'],
862 sel = fields_def[field[len(prefix)]]['selection']
864 sel = fields_def[field[len(prefix)]]['selection'](self,
867 if line[i] in [tools.ustr(key), tools.ustr(val)]: #Acepting key or value for selection field
870 if line[i] and not res:
871 logger.notifyChannel("import", netsvc.LOG_WARNING,
872 _("key '%s' not found in selection field '%s'") % \
873 (line[i], field[len(prefix)]))
875 warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
877 elif fields_def[field[len(prefix)]]['type'] == 'many2one':
880 relation = fields_def[field[len(prefix)]]['relation']
881 res2 = self.pool.get(relation).name_search(cr, uid,
882 line[i], [], operator='=', context=context)
883 res = (res2 and res2[0][0]) or False
885 warning += [_("Relation not found: %s on '%s'") % (line[i], relation)]
886 logger.notifyChannel("import", netsvc.LOG_WARNING,
887 _("Relation not found: %s on '%s'") % (line[i], relation))
888 elif fields_def[field[len(prefix)]]['type'] == 'many2many':
891 relation = fields_def[field[len(prefix)]]['relation']
892 for word in line[i].split(config.get('csv_internal_sep')):
893 res2 = self.pool.get(relation).name_search(cr,
894 uid, word, [], operator='=', context=context)
895 res3 = (res2 and res2[0][0]) or False
897 warning += [_("Relation not found: %s on '%s'") % (line[i], relation)]
898 logger.notifyChannel("import",
900 _("Relation not found: %s on '%s'") % (line[i], relation))
906 res = line[i] or False
907 row[field[len(prefix)]] = res
908 elif (prefix==field[0:len(prefix)]):
909 if field[0] not in todo:
910 todo.append(field[len(prefix)])
912 # Import one2many, many2many fields
916 relation_obj = self.pool.get(fields_def[field]['relation'])
917 newfd = relation_obj.fields_get(
918 cr, uid, context=context)
919 res = process_liness(self, datas, prefix + [field], current_module, relation_obj._name, newfd, position)
920 (newrow, max2, w2, translate2, data_id2, data_res_id2) = res
921 nbrmax = max(nbrmax, max2)
922 warning = warning + w2
923 reduce(lambda x, y: x and y, newrow)
924 row[field] = newrow and (reduce(lambda x, y: x or y, newrow.values()) and \
925 [(data_res_id2 and 1 or 0, data_res_id2 or 0, newrow)]) or []
927 while (position+i) < len(datas):
929 for j in range(len(fields)):
931 if (len(field2) <= (len(prefix)+1)) and datas[position+i][j]:
936 (newrow, max2, w2, translate2, data_id2, data_res_id2) = process_liness(
937 self, datas, prefix+[field], current_module, relation_obj._name, newfd, position+i)
938 warning = warning + w2
939 if newrow and reduce(lambda x, y: x or y, newrow.values()):
940 row[field].append((data_res_id2 and 1 or 0, data_res_id2 or 0, newrow))
942 nbrmax = max(nbrmax, i)
945 for i in range(max(nbrmax, 1)):
948 result = (row, nbrmax, warning, translate, data_id, data_res_id)
951 fields_def = self.fields_get(cr, uid, context=context)
954 initial_size = len(datas)
955 if config.get('import_partial', False) and filename:
956 data = pickle.load(file(config.get('import_partial')))
957 original_value = data.get(filename, 0)
963 (res, other, warning, translate, data_id, res_id) = \
964 process_liness(self, datas, [], current_module, self._name, fields_def)
967 return (-1, res, 'Line ' + str(counter) +' : ' + '!\n'.join(warning), '')
970 id = ir_model_data_obj._update(cr, uid, self._name,
971 current_module, res, xml_id=data_id, mode=mode,
972 noupdate=noupdate, res_id=res_id, context=context)
977 if isinstance(e, psycopg2.IntegrityError):
978 msg = _('Insertion Failed! ')
979 for key in self.pool._sql_error.keys():
981 msg = self.pool._sql_error[key]
982 if hasattr(msg, '__call__'):
983 msg = msg(cr, uid, [res_id,], context=context)
987 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '')
988 if isinstance(e, osv.orm.except_orm):
989 msg = _('Insertion Failed! ' + e[1])
990 return (-1, res, 'Line ' + str(counter) +' : ' + msg, '')
991 #Raising Uncaught exception
992 return (-1, res, 'Line ' + str(counter) +' : ' + str(e), '')
994 for lang in translate:
995 context2 = context.copy()
996 context2['lang'] = lang
997 self.write(cr, uid, [id], translate[lang], context2)
998 if config.get('import_partial', False) and filename and (not (counter%100)):
999 data = pickle.load(file(config.get('import_partial')))
1000 data[filename] = initial_size - len(datas) + original_value
1001 pickle.dump(data, file(config.get('import_partial'), 'wb'))
1002 if context.get('defer_parent_store_computation'):
1003 self._parent_store_compute(cr)
1006 #except Exception, e:
1007 # logger.notifyChannel("import", netsvc.LOG_ERROR, e)
1010 # return (-1, res, e[0], warning)
1012 # return (-1, res, e[0], '')
1015 # TODO: Send a request with the result and multi-thread !
1017 if context.get('defer_parent_store_computation'):
1018 self._parent_store_compute(cr)
1019 return (done, 0, 0, 0)
1021 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1023 Read records with given ids with the given fields
1025 :param cr: database cursor
1026 :param user: current user id
1027 :param ids: id or list of the ids of the records to read
1028 :param fields: optional list of field names to return (default: all fields would be returned)
1029 :type fields: list (example ['field_name_1', ...])
1030 :param context: optional context dictionary - it may contains keys for specifying certain options
1031 like ``context_lang``, ``context_tz`` to alter the results of the call.
1032 A special ``bin_size`` boolean flag may also be passed in the context to request the
1033 value of all fields.binary columns to be returned as the size of the binary instead of its
1034 contents. This can also be selectively overriden by passing a field-specific flag
1035 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1036 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1037 :return: list of dictionaries((dictionary per record asked)) with requested field values
1038 :rtype: [{‘name_of_the_field’: value, ...}, ...]
1039 :raise AccessError: * if user has no read rights on the requested object
1040 * if user tries to bypass access rules for read on the requested object
1043 raise NotImplementedError(_('The read method is not implemented on this object !'))
1045 def get_invalid_fields(self, cr, uid):
1046 return list(self._invalids)
1048 def _validate(self, cr, uid, ids, context=None):
1049 context = context or {}
1050 lng = context.get('lang', False) or 'en_US'
1051 trans = self.pool.get('ir.translation')
1053 for constraint in self._constraints:
1054 fun, msg, fields = constraint
1055 if not fun(self, cr, uid, ids):
1056 # Check presence of __call__ directly instead of using
1057 # callable() because it will be deprecated as of Python 3.0
1058 if hasattr(msg, '__call__'):
1059 tmp_msg = msg(self, cr, uid, ids, context=context)
1060 if isinstance(tmp_msg, tuple):
1061 tmp_msg, params = tmp_msg
1062 translated_msg = tmp_msg % params
1064 translated_msg = tmp_msg
1066 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1068 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1070 self._invalids.update(fields)
1073 raise except_orm('ValidateError', '\n'.join(error_msgs))
1075 self._invalids.clear()
1077 def default_get(self, cr, uid, fields_list, context=None):
1079 Returns default values for the fields in fields_list.
1081 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1082 :type fields_list: list
1083 :param context: optional context dictionary - it may contains keys for specifying certain options
1084 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1085 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1086 or override a default value for a field.
1087 A special ``bin_size`` boolean flag may also be passed in the context to request the
1088 value of all fields.binary columns to be returned as the size of the binary instead of its
1089 contents. This can also be selectively overriden by passing a field-specific flag
1090 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1091 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1092 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1094 # trigger view init hook
1095 self.view_init(cr, uid, fields_list, context)
1101 # get the default values for the inherited fields
1102 for t in self._inherits.keys():
1103 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1106 # get the default values defined in the object
1107 for f in fields_list:
1108 if f in self._defaults:
1109 if callable(self._defaults[f]):
1110 defaults[f] = self._defaults[f](self, cr, uid, context)
1112 defaults[f] = self._defaults[f]
1114 fld_def = ((f in self._columns) and self._columns[f]) \
1115 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1118 if isinstance(fld_def, fields.property):
1119 property_obj = self.pool.get('ir.property')
1120 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1122 if isinstance(prop_value, (browse_record, browse_null)):
1123 defaults[f] = prop_value.id
1125 defaults[f] = prop_value
1127 if f not in defaults:
1130 # get the default values set by the user and override the default
1131 # values defined in the object
1132 ir_values_obj = self.pool.get('ir.values')
1133 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1134 for id, field, field_value in res:
1135 if field in fields_list:
1136 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1137 if fld_def._type in ('many2one', 'one2one'):
1138 obj = self.pool.get(fld_def._obj)
1139 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1141 if fld_def._type in ('many2many'):
1142 obj = self.pool.get(fld_def._obj)
1144 for i in range(len(field_value)):
1145 if not obj.search(cr, uid, [('id', '=',
1148 field_value2.append(field_value[i])
1149 field_value = field_value2
1150 if fld_def._type in ('one2many'):
1151 obj = self.pool.get(fld_def._obj)
1153 for i in range(len(field_value)):
1154 field_value2.append({})
1155 for field2 in field_value[i]:
1156 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1157 obj2 = self.pool.get(obj._columns[field2]._obj)
1158 if not obj2.search(cr, uid,
1159 [('id', '=', field_value[i][field2])]):
1161 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1162 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1163 if not obj2.search(cr, uid,
1164 [('id', '=', field_value[i][field2])]):
1166 # TODO add test for many2many and one2many
1167 field_value2[i][field2] = field_value[i][field2]
1168 field_value = field_value2
1169 defaults[field] = field_value
1171 # get the default values from the context
1172 for key in context or {}:
1173 if key.startswith('default_') and (key[8:] in fields_list):
1174 defaults[key[8:]] = context[key]
1178 def perm_read(self, cr, user, ids, context=None, details=True):
1179 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1181 def unlink(self, cr, uid, ids, context=None):
1182 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1184 def write(self, cr, user, ids, vals, context=None):
1185 raise NotImplementedError(_('The write method is not implemented on this object !'))
1187 def create(self, cr, user, vals, context=None):
1188 raise NotImplementedError(_('The create method is not implemented on this object !'))
1190 def fields_get_keys(self, cr, user, context=None):
1191 res = self._columns.keys()
1192 for parent in self._inherits:
1193 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1196 # returns the definition of each field in the object
1197 # the optional fields parameter can limit the result to some fields
1198 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1202 translation_obj = self.pool.get('ir.translation')
1203 for parent in self._inherits:
1204 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1206 if self._columns.keys():
1207 for f in self._columns.keys():
1208 field_col = self._columns[f]
1209 if allfields and f not in allfields:
1211 res[f] = {'type': field_col._type}
1212 # This additional attributes for M2M and function field is added
1213 # because we need to display tooltip with this additional information
1214 # when client is started in debug mode.
1215 if isinstance(field_col, fields.function):
1216 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1217 res[f]['store'] = field_col.store
1218 if isinstance(field_col.store, dict):
1219 res[f]['store'] = str(field_col.store)
1220 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1221 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1222 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1223 res[f]['func_obj'] = field_col._obj or False
1224 res[f]['func_method'] = field_col._method
1225 if isinstance(field_col, fields.many2many):
1226 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1227 res[f]['third_table'] = field_col._rel
1228 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1229 'change_default', 'translate', 'help', 'select', 'selectable'):
1230 if getattr(field_col, arg):
1231 res[f][arg] = getattr(field_col, arg)
1232 if not write_access:
1233 res[f]['readonly'] = True
1234 res[f]['states'] = {}
1235 for arg in ('digits', 'invisible', 'filters'):
1236 if getattr(field_col, arg, None):
1237 res[f][arg] = getattr(field_col, arg)
1239 if field_col.string:
1240 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1242 res[f]['string'] = res_trans
1244 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1246 res[f]['help'] = help_trans
1248 if hasattr(field_col, 'selection'):
1249 if isinstance(field_col.selection, (tuple, list)):
1250 sel = field_col.selection
1251 # translate each selection option
1253 for (key, val) in sel:
1256 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1257 sel2.append((key, val2 or val))
1259 res[f]['selection'] = sel
1261 # call the 'dynamic selection' function
1262 res[f]['selection'] = field_col.selection(self, cr, user, context)
1263 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1264 res[f]['relation'] = field_col._obj
1265 res[f]['domain'] = field_col._domain
1266 res[f]['context'] = field_col._context
1268 #TODO : read the fields from the database
1272 # filter out fields which aren't in the fields list
1273 for r in res.keys():
1274 if r not in allfields:
1279 # Overload this method if you need a window title which depends on the context
1281 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1284 def __view_look_dom(self, cr, user, node, view_id, context=None):
1292 if isinstance(s, unicode):
1293 return s.encode('utf8')
1296 # return True if node can be displayed to current user
1297 def check_group(node):
1298 if node.get('groups'):
1299 groups = node.get('groups').split(',')
1300 access_pool = self.pool.get('ir.model.access')
1301 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1303 node.set('invisible', '1')
1304 if 'attrs' in node.attrib:
1305 del(node.attrib['attrs']) #avoid making field visible later
1306 del(node.attrib['groups'])
1311 if node.tag in ('field', 'node', 'arrow'):
1312 if node.get('object'):
1317 if f.tag in ('field'):
1318 xml += etree.tostring(f, encoding="utf-8")
1320 new_xml = etree.fromstring(encode(xml))
1321 ctx = context.copy()
1322 ctx['base_model_name'] = self._name
1323 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1328 attrs = {'views': views}
1330 if node.get('name'):
1333 if node.get('name') in self._columns:
1334 column = self._columns[node.get('name')]
1336 column = self._inherit_fields[node.get('name')][2]
1341 relation = self.pool.get(column._obj)
1346 if f.tag in ('form', 'tree', 'graph'):
1348 ctx = context.copy()
1349 ctx['base_model_name'] = self._name
1350 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1351 views[str(f.tag)] = {
1355 attrs = {'views': views}
1356 if node.get('widget') and node.get('widget') == 'selection':
1357 # Prepare the cached selection list for the client. This needs to be
1358 # done even when the field is invisible to the current user, because
1359 # other events could need to change its value to any of the selectable ones
1360 # (such as on_change events, refreshes, etc.)
1362 # If domain and context are strings, we keep them for client-side, otherwise
1363 # we evaluate them server-side to consider them when generating the list of
1365 # TODO: find a way to remove this hack, by allow dynamic domains
1367 if column._domain and not isinstance(column._domain, basestring):
1368 dom = column._domain
1369 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1370 search_context = dict(context)
1371 if column._context and not isinstance(column._context, basestring):
1372 search_context.update(column._context)
1373 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1374 if (node.get('required') and not int(node.get('required'))) or not column.required:
1375 attrs['selection'].append((False, ''))
1376 fields[node.get('name')] = attrs
1378 elif node.tag in ('form', 'tree'):
1379 result = self.view_header_get(cr, user, False, node.tag, context)
1381 node.set('string', result)
1383 elif node.tag == 'calendar':
1384 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1385 if node.get(additional_field):
1386 fields[node.get(additional_field)] = {}
1388 if 'groups' in node.attrib:
1392 if ('lang' in context) and not result:
1393 if node.get('string'):
1394 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1395 if trans == node.get('string') and ('base_model_name' in context):
1396 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1397 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1398 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1400 node.set('string', trans)
1401 if node.get('confirm'):
1402 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1404 node.set('confirm', trans)
1406 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1408 node.set('sum', trans)
1411 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1412 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1416 def _disable_workflow_buttons(self, cr, user, node):
1418 # admin user can always activate workflow buttons
1421 # TODO handle the case of more than one workflow for a model or multiple
1422 # transitions with different groups and same signal
1423 usersobj = self.pool.get('res.users')
1424 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1425 for button in buttons:
1426 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1427 cr.execute("""SELECT DISTINCT t.group_id
1429 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1430 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1433 AND t.group_id is NOT NULL
1434 """, (self._name, button.get('name')))
1435 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1436 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1437 button.set('readonly', str(int(not can_click)))
1440 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1441 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1442 node = self._disable_workflow_buttons(cr, user, node)
1443 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1445 if node.tag == 'diagram':
1446 if node.getchildren()[0].tag == 'node':
1447 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1448 if node.getchildren()[1].tag == 'arrow':
1449 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1450 for key, value in node_fields.items():
1452 for key, value in arrow_fields.items():
1455 fields = self.fields_get(cr, user, fields_def.keys(), context)
1456 for field in fields_def:
1458 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1459 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1460 elif field in fields:
1461 fields[field].update(fields_def[field])
1463 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))
1464 res = cr.fetchall()[:]
1466 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1467 msg = "\n * ".join([r[0] for r in res])
1468 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1469 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1470 raise except_orm('View error', msg)
1473 def __get_default_calendar_view(self):
1474 """Generate a default calendar view (For internal use only).
1477 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1478 '<calendar string="%s"') % (self._description)
1480 if (self._date_name not in self._columns):
1482 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1483 if dt in self._columns:
1484 self._date_name = dt
1489 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1492 arch += ' date_start="%s"' % (self._date_name)
1494 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1495 if color in self._columns:
1496 arch += ' color="' + color + '"'
1499 dt_stop_flag = False
1501 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1502 if dt_stop in self._columns:
1503 arch += ' date_stop="' + dt_stop + '"'
1507 if not dt_stop_flag:
1508 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1509 if dt_delay in self._columns:
1510 arch += ' date_delay="' + dt_delay + '"'
1514 ' <field name="%s"/>\n'
1515 '</calendar>') % (self._rec_name)
1519 def __get_default_search_view(self, cr, uid, context=None):
1520 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1521 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1523 fields_to_search = set()
1524 fields = self.fields_get(cr, uid, context=context)
1525 for field in fields:
1526 if fields[field].get('select'):
1527 fields_to_search.add(field)
1528 for view in (form_view, tree_view):
1529 view_root = etree.fromstring(view['arch'])
1530 # Only care about select=1 in xpath below, because select=2 is covered
1531 # by the custom advanced search in clients
1532 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1534 tree_view_root = view_root # as provided by loop above
1535 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1536 field_group = etree.Element("group")
1537 search_view.append(field_group)
1539 for field_name in fields_to_search:
1540 field_group.append(etree.Element("field", attrib={'name': field_name}))
1542 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1545 # if view_id, view_type is not required
1547 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1549 Get the detailed composition of the requested view like fields, model, view architecture
1551 :param cr: database cursor
1552 :param user: current user id
1553 :param view_id: id of the view or None
1554 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1555 :param context: context arguments, like lang, time zone
1556 :param toolbar: true to include contextual actions
1557 :param submenu: example (portal_project module)
1558 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1559 :raise AttributeError:
1560 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1561 * if some tag other than 'position' is found in parent view
1562 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1569 if isinstance(s, unicode):
1570 return s.encode('utf8')
1573 def raise_view_error(error_msg, child_view_id):
1574 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1575 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1576 % { 'xml_id': child_view.xml_id,
1577 'parent_xml_id': view.xml_id,
1578 'model': self._name, })
1580 def _inherit_apply(src, inherit, inherit_id=None):
1581 def _find(node, node2):
1582 if node2.tag == 'xpath':
1583 res = node.xpath(node2.get('expr'))
1589 for n in node.getiterator(node2.tag):
1591 if node2.tag == 'field':
1592 # only compare field names, a field can be only once in a given view
1593 # at a given level (and for multilevel expressions, we should use xpath
1594 # inheritance spec anyway)
1595 if node2.get('name') == n.get('name'):
1599 for attr in node2.attrib:
1600 if attr == 'position':
1603 if n.get(attr) == node2.get(attr):
1610 # End: _find(node, node2)
1612 doc_dest = etree.fromstring(encode(inherit))
1613 toparse = [doc_dest]
1616 node2 = toparse.pop(0)
1617 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1619 if node2.tag == 'data':
1620 toparse += [ c for c in doc_dest ]
1622 node = _find(src, node2)
1623 if node is not None:
1625 if node2.get('position'):
1626 pos = node2.get('position')
1627 if pos == 'replace':
1628 parent = node.getparent()
1630 src = copy.deepcopy(node2[0])
1633 node.addprevious(child)
1634 node.getparent().remove(node)
1635 elif pos == 'attributes':
1636 for child in node2.getiterator('attribute'):
1637 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1639 node.set(attribute[0], attribute[1])
1641 del(node.attrib[attribute[0]])
1643 sib = node.getnext()
1647 elif pos == 'after':
1652 sib.addprevious(child)
1653 elif pos == 'before':
1654 node.addprevious(child)
1656 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1659 ' %s="%s"' % (attr, node2.get(attr))
1660 for attr in node2.attrib
1661 if attr != 'position'
1663 tag = "<%s%s>" % (node2.tag, attrs)
1664 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1666 # End: _inherit_apply(src, inherit)
1668 result = {'type': view_type, 'model': self._name}
1673 parent_view_model = None
1675 view_ref = context.get(view_type + '_view_ref', False)
1676 if view_ref and not view_id:
1678 module, view_ref = view_ref.split('.', 1)
1679 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1680 view_ref_res = cr.fetchone()
1682 view_id = view_ref_res[0]
1685 query = "SELECT arch,name,field_parent,id,type,inherit_id,model FROM ir_ui_view WHERE id=%s"
1688 query += " AND model=%s"
1689 params += (self._name,)
1690 cr.execute(query, params)
1692 cr.execute('''SELECT
1693 arch,name,field_parent,id,type,inherit_id,model
1700 ORDER BY priority''', (self._name, view_type))
1701 sql_res = cr.fetchone()
1707 view_id = ok or sql_res[3]
1709 parent_view_model = sql_res[6]
1711 # if a view was found
1713 result['type'] = sql_res[4]
1714 result['view_id'] = sql_res[3]
1715 result['arch'] = sql_res[0]
1717 def _inherit_apply_rec(result, inherit_id):
1718 # get all views which inherit from (ie modify) this view
1719 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1720 sql_inherit = cr.fetchall()
1721 for (inherit, id) in sql_inherit:
1722 result = _inherit_apply(result, inherit, id)
1723 result = _inherit_apply_rec(result, id)
1726 inherit_result = etree.fromstring(encode(result['arch']))
1727 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1729 result['name'] = sql_res[1]
1730 result['field_parent'] = sql_res[2] or False
1733 # otherwise, build some kind of default view
1734 if view_type == 'form':
1735 res = self.fields_get(cr, user, context=context)
1736 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1737 '<form string="%s">' % (self._description,)
1739 if res[x]['type'] not in ('one2many', 'many2many'):
1740 xml += '<field name="%s"/>' % (x,)
1741 if res[x]['type'] == 'text':
1745 elif view_type == 'tree':
1746 _rec_name = self._rec_name
1747 if _rec_name not in self._columns:
1748 _rec_name = self._columns.keys()[0]
1749 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1750 '<tree string="%s"><field name="%s"/></tree>' \
1751 % (self._description, self._rec_name)
1753 elif view_type == 'calendar':
1754 xml = self.__get_default_calendar_view()
1756 elif view_type == 'search':
1757 xml = self.__get_default_search_view(cr, user, context)
1760 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1761 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1762 result['arch'] = etree.fromstring(encode(xml))
1763 result['name'] = 'default'
1764 result['field_parent'] = False
1765 result['view_id'] = 0
1767 if parent_view_model != self._name:
1768 ctx = context.copy()
1769 ctx['base_model_name'] = parent_view_model
1772 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1773 result['arch'] = xarch
1774 result['fields'] = xfields
1777 if context and context.get('active_id', False):
1778 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1780 act_id = data_menu.id
1782 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1783 result['submenu'] = getattr(data_action, 'menus', False)
1787 for key in ('report_sxw_content', 'report_rml_content',
1788 'report_sxw', 'report_rml',
1789 'report_sxw_content_data', 'report_rml_content_data'):
1793 ir_values_obj = self.pool.get('ir.values')
1794 resprint = ir_values_obj.get(cr, user, 'action',
1795 'client_print_multi', [(self._name, False)], False,
1797 resaction = ir_values_obj.get(cr, user, 'action',
1798 'client_action_multi', [(self._name, False)], False,
1801 resrelate = ir_values_obj.get(cr, user, 'action',
1802 'client_action_relate', [(self._name, False)], False,
1804 resprint = map(clean, resprint)
1805 resaction = map(clean, resaction)
1806 resaction = filter(lambda x: not x.get('multi', False), resaction)
1807 resprint = filter(lambda x: not x.get('multi', False), resprint)
1808 resrelate = map(lambda x: x[2], resrelate)
1810 for x in resprint + resaction + resrelate:
1811 x['string'] = x['name']
1813 result['toolbar'] = {
1815 'action': resaction,
1820 _view_look_dom_arch = __view_look_dom_arch
1822 def search_count(self, cr, user, args, context=None):
1825 res = self.search(cr, user, args, context=context, count=True)
1826 if isinstance(res, list):
1830 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1832 Search for records based on a search domain.
1834 :param cr: database cursor
1835 :param user: current user id
1836 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1837 :param offset: optional number of results to skip in the returned values (default: 0)
1838 :param limit: optional max number of records to return (default: **None**)
1839 :param order: optional columns to sort by (default: self._order=id )
1840 :param context: optional context arguments, like lang, time zone
1841 :type context: dictionary
1842 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1843 :return: id or list of ids of records matching the criteria
1844 :rtype: integer or list of integers
1845 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1847 **Expressing a search domain (args)**
1849 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1851 * **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.
1852 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1853 The semantics of most of these operators are obvious.
1854 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1855 according to the semantics of this model (i.e following the relationship field named by
1856 ``self._parent_name``, by default ``parent_id``.
1857 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1859 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1860 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1861 Be very careful about this when you combine them the first time.
1863 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1865 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1867 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::
1869 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1872 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1874 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1876 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1877 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1878 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1880 :param access_rights_uid: optional user ID to use when checking access rights
1881 (not for ir.rules, this is only for ir.model.access)
1883 raise NotImplementedError(_('The search method is not implemented on this object !'))
1885 def name_get(self, cr, user, ids, context=None):
1888 :param cr: database cursor
1889 :param user: current user id
1891 :param ids: list of ids
1892 :param context: context arguments, like lang, time zone
1893 :type context: dictionary
1894 :return: tuples with the text representation of requested objects for to-many relationships
1901 if isinstance(ids, (int, long)):
1903 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1904 [self._rec_name], context, load='_classic_write')]
1906 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1908 Search for records and their display names according to a search domain.
1910 :param cr: database cursor
1911 :param user: current user id
1912 :param name: object name to search
1913 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1914 :param operator: operator for search criterion
1915 :param context: context arguments, like lang, time zone
1916 :type context: dictionary
1917 :param limit: optional max number of records to return
1918 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1920 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1921 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1924 return self._name_search(cr, user, name, args, operator, context, limit)
1926 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1927 # solve some access rights issues
1928 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1935 args += [(self._rec_name, operator, name)]
1936 access_rights_uid = name_get_uid or user
1937 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1938 res = self.name_get(cr, access_rights_uid, ids, context)
1941 def copy(self, cr, uid, id, default=None, context=None):
1942 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1944 def exists(self, cr, uid, id, context=None):
1945 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1947 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1950 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1952 fields = self._columns.keys() + self._inherit_fields.keys()
1953 #FIXME: collect all calls to _get_source into one SQL call.
1955 res[lang] = {'code': lang}
1957 if f in self._columns:
1958 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1960 res[lang][f] = res_trans
1962 res[lang][f] = self._columns[f].string
1963 for table in self._inherits:
1964 cols = intersect(self._inherit_fields.keys(), fields)
1965 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
1968 res[lang]['code'] = lang
1969 for f in res2[lang]:
1970 res[lang][f] = res2[lang][f]
1973 def write_string(self, cr, uid, id, langs, vals, context=None):
1974 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
1975 #FIXME: try to only call the translation in one SQL
1978 if field in self._columns:
1979 src = self._columns[field].string
1980 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1981 for table in self._inherits:
1982 cols = intersect(self._inherit_fields.keys(), vals)
1984 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
1987 def _check_removed_columns(self, cr, log=False):
1988 raise NotImplementedError()
1990 def _add_missing_default_values(self, cr, uid, values, context=None):
1991 missing_defaults = []
1992 avoid_tables = [] # avoid overriding inherited values when parent is set
1993 for tables, parent_field in self._inherits.items():
1994 if parent_field in values:
1995 avoid_tables.append(tables)
1996 for field in self._columns.keys():
1997 if not field in values:
1998 missing_defaults.append(field)
1999 for field in self._inherit_fields.keys():
2000 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
2001 missing_defaults.append(field)
2003 if len(missing_defaults):
2004 # override defaults with the provided values, never allow the other way around
2005 defaults = self.default_get(cr, uid, missing_defaults, context)
2007 if (dv in self._columns and self._columns[dv]._type == 'many2many') \
2008 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many') \
2009 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
2010 defaults[dv] = [(6, 0, defaults[dv])]
2011 if dv in self._columns and self._columns[dv]._type == 'one2many' \
2012 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many') \
2013 and isinstance(defaults[dv], (list, tuple)) and isinstance(defaults[dv][0], dict):
2014 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
2015 defaults.update(values)
2019 class orm_memory(orm_template):
2021 _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']
2022 _inherit_fields = {}
2027 def __init__(self, cr):
2028 super(orm_memory, self).__init__(cr)
2032 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2034 def _check_access(self, uid, object_id, mode):
2035 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2036 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2038 def vaccum(self, cr, uid):
2040 if self.check_id % self._check_time:
2043 max = time.time() - self._max_hours * 60 * 60
2044 for id in self.datas:
2045 if self.datas[id]['internal.date_access'] < max:
2047 self.unlink(cr, 1, tounlink)
2048 if len(self.datas) > self._max_count:
2049 sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items())
2051 ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count])
2052 self.unlink(cr, uid, ids)
2055 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2058 if not fields_to_read:
2059 fields_to_read = self._columns.keys()
2063 if isinstance(ids, (int, long)):
2067 for f in fields_to_read:
2068 record = self.datas.get(id)
2070 self._check_access(user, id, 'read')
2071 r[f] = record.get(f, False)
2072 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2075 if id in self.datas:
2076 self.datas[id]['internal.date_access'] = time.time()
2077 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2078 for f in fields_post:
2079 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2080 for record in result:
2081 record[f] = res2[record['id']]
2082 if isinstance(ids_orig, (int, long)):
2086 def write(self, cr, user, ids, vals, context=None):
2092 if self._columns[field]._classic_write:
2093 vals2[field] = vals[field]
2095 upd_todo.append(field)
2096 for object_id in ids:
2097 self._check_access(user, object_id, mode='write')
2098 self.datas[object_id].update(vals2)
2099 self.datas[object_id]['internal.date_access'] = time.time()
2100 for field in upd_todo:
2101 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2102 self._validate(cr, user, [object_id], context)
2103 wf_service = netsvc.LocalService("workflow")
2104 wf_service.trg_write(user, self._name, object_id, cr)
2107 def create(self, cr, user, vals, context=None):
2108 self.vaccum(cr, user)
2110 id_new = self.next_id
2112 vals = self._add_missing_default_values(cr, user, vals, context)
2117 if self._columns[field]._classic_write:
2118 vals2[field] = vals[field]
2120 upd_todo.append(field)
2121 self.datas[id_new] = vals2
2122 self.datas[id_new]['internal.date_access'] = time.time()
2123 self.datas[id_new]['internal.create_uid'] = user
2125 for field in upd_todo:
2126 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2127 self._validate(cr, user, [id_new], context)
2128 if self._log_create and not (context and context.get('no_store_function', False)):
2129 message = self._description + \
2131 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2133 self.log(cr, user, id_new, message, True, context=context)
2134 wf_service = netsvc.LocalService("workflow")
2135 wf_service.trg_create(user, self._name, id_new, cr)
2138 def _where_calc(self, cr, user, args, active_test=True, context=None):
2143 # if the object has a field named 'active', filter out all inactive
2144 # records unless they were explicitely asked for
2145 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2147 active_in_args = False
2149 if a[0] == 'active':
2150 active_in_args = True
2151 if not active_in_args:
2152 args.insert(0, ('active', '=', 1))
2154 args = [('active', '=', 1)]
2157 e = expression.expression(args)
2158 e.parse(cr, user, self, context)
2162 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2166 # implicit filter on current user except for superuser
2170 args.insert(0, ('internal.create_uid', '=', user))
2172 result = self._where_calc(cr, user, args, context=context)
2174 return self.datas.keys()
2178 #Find the value of dict
2181 for id, data in self.datas.items():
2182 counter = counter + 1
2184 if limit and (counter > int(limit)):
2189 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2190 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2191 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2192 elif arg[1] in ['ilike']:
2193 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2203 def unlink(self, cr, uid, ids, context=None):
2205 self._check_access(uid, id, 'unlink')
2206 self.datas.pop(id, None)
2208 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2211 def perm_read(self, cr, user, ids, context=None, details=True):
2213 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2214 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2216 self._check_access(user, id, 'read')
2218 'create_uid': credentials,
2219 'create_date': create_date,
2221 'write_date': False,
2227 def _check_removed_columns(self, cr, log=False):
2228 # nothing to check in memory...
2231 def exists(self, cr, uid, id, context=None):
2232 return id in self.datas
2234 class orm(orm_template):
2235 _sql_constraints = []
2237 _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']
2238 __logger = logging.getLogger('orm')
2239 __schema = logging.getLogger('orm.schema')
2240 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2242 Get the list of records in list view grouped by the given ``groupby`` fields
2244 :param cr: database cursor
2245 :param uid: current user id
2246 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2247 :param fields: list of fields present in the list view specified on the object
2248 :param groupby: list of fields on which to groupby the records
2249 :type fields_list: list (example ['field_name_1', ...])
2250 :param offset: optional number of records to skip
2251 :param limit: optional max number of records to return
2252 :param context: context arguments, like lang, time zone
2253 :param order: optional ``order by`` specification, for overriding the natural
2254 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2255 (supported only for many2one fields currently)
2256 :return: list of dictionaries(one dictionary for each record) containing:
2258 * the values of fields grouped by the fields in ``groupby`` argument
2259 * __domain: list of tuples specifying the search criteria
2260 * __context: dictionary with argument like ``groupby``
2261 :rtype: [{'field_name_1': value, ...]
2262 :raise AccessError: * if user has no read rights on the requested object
2263 * if user tries to bypass access rules for read on the requested object
2266 context = context or {}
2267 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2269 fields = self._columns.keys()
2271 query = self._where_calc(cr, uid, domain, context=context)
2272 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2274 # Take care of adding join(s) if groupby is an '_inherits'ed field
2275 groupby_list = groupby
2277 if isinstance(groupby, list):
2278 groupby = groupby[0]
2279 self._inherits_join_calc(groupby, query)
2282 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?)"
2283 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2284 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"
2286 fget = self.fields_get(cr, uid, fields)
2287 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2289 group_count = group_by = groupby
2291 if fget.get(groupby):
2292 if fget[groupby]['type'] in ('date', 'datetime'):
2293 flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
2294 groupby = "to_char(%s,'yyyy-mm')" % (groupby)
2298 # Don't allow arbitrary values, as this would be a SQL injection vector!
2299 raise except_orm(_('Invalid group_by'),
2300 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2303 fields_pre = [f for f in float_int_fields if
2304 f == self.CONCURRENCY_CHECK_FIELD
2305 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2306 for f in fields_pre:
2307 if f not in ['id', 'sequence']:
2308 group_operator = fget[f].get('group_operator', 'sum')
2311 flist += group_operator+'('+f+') as '+f
2313 gb = groupby and (' GROUP BY '+groupby) or ''
2315 from_clause, where_clause, where_clause_params = query.get_sql()
2316 where_clause = where_clause and ' WHERE ' + where_clause
2317 limit_str = limit and ' limit %d' % limit or ''
2318 offset_str = offset and ' offset %d' % offset or ''
2319 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2321 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)
2324 for r in cr.dictfetchall():
2325 for fld, val in r.items():
2326 if val == None: r[fld] = False
2327 alldata[r['id']] = r
2330 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2331 # the IDS of records that have groupby field value = False or '' should be sorted too
2332 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2333 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2334 # 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):
2335 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2339 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2340 if not isinstance(groupby_list, (str, unicode)):
2341 if groupby or not context.get('group_by_no_leaf', False):
2342 d['__context'] = {'group_by': groupby_list[1:]}
2343 if groupby and groupby in fget:
2344 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2345 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2346 days = calendar.monthrange(dt.year, dt.month)[1]
2348 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2349 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),\
2350 (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
2351 del alldata[d['id']][groupby]
2352 d.update(alldata[d['id']])
2356 def _inherits_join_add(self, parent_model_name, query):
2358 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2360 :param parent_model_name: name of the parent model for which the clauses should be added
2361 :param query: query object on which the JOIN should be added
2363 inherits_field = self._inherits[parent_model_name]
2364 parent_model = self.pool.get(parent_model_name)
2365 parent_table_name = parent_model._table
2366 quoted_parent_table_name = '"%s"' % parent_table_name
2367 if quoted_parent_table_name not in query.tables:
2368 query.tables.append(quoted_parent_table_name)
2369 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2371 def _inherits_join_calc(self, field, query):
2373 Adds missing table select and join clause(s) to ``query`` for reaching
2374 the field coming from an '_inherits' parent table (no duplicates).
2376 :param field: name of inherited field to reach
2377 :param query: query object on which the JOIN should be added
2378 :return: qualified name of field, to be used in SELECT clause
2380 current_table = self
2381 while field in current_table._inherit_fields and not field in current_table._columns:
2382 parent_model_name = current_table._inherit_fields[field][0]
2383 parent_table = self.pool.get(parent_model_name)
2384 self._inherits_join_add(parent_model_name, query)
2385 current_table = parent_table
2386 return '"%s".%s' % (current_table._table, field)
2388 def _parent_store_compute(self, cr):
2389 if not self._parent_store:
2391 logger = netsvc.Logger()
2392 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2393 def browse_rec(root, pos=0):
2395 where = self._parent_name+'='+str(root)
2397 where = self._parent_name+' IS NULL'
2398 if self._parent_order:
2399 where += ' order by '+self._parent_order
2400 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2402 for id in cr.fetchall():
2403 pos2 = browse_rec(id[0], pos2)
2404 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2406 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2407 if self._parent_order:
2408 query += ' order by ' + self._parent_order
2411 for (root,) in cr.fetchall():
2412 pos = browse_rec(root, pos)
2415 def _update_store(self, cr, f, k):
2416 logger = netsvc.Logger()
2417 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2418 ss = self._columns[k]._symbol_set
2419 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2420 cr.execute('select id from '+self._table)
2421 ids_lst = map(lambda x: x[0], cr.fetchall())
2424 ids_lst = ids_lst[40:]
2425 res = f.get(cr, self, iids, k, 1, {})
2426 for key, val in res.items():
2429 # if val is a many2one, just write the ID
2430 if type(val) == tuple:
2432 if (val<>False) or (type(val)<>bool):
2433 cr.execute(update_query, (ss[1](val), key))
2435 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2436 """Raise except_orm if value is not among the valid values for the selection field"""
2437 if self._columns[field]._type == 'reference':
2438 val_model, val_id_str = value.split(',', 1)
2441 val_id = long(val_id_str)
2445 raise except_orm(_('ValidateError'),
2446 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2450 if isinstance(self._columns[field].selection, (tuple, list)):
2451 if val in dict(self._columns[field].selection):
2453 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2455 raise except_orm(_('ValidateError'),
2456 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2458 def _check_removed_columns(self, cr, log=False):
2459 # iterate on the database columns to drop the NOT NULL constraints
2460 # of fields which were required but have been removed (or will be added by another module)
2461 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2462 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2463 cr.execute("SELECT a.attname, a.attnotnull"
2464 " FROM pg_class c, pg_attribute a"
2465 " WHERE c.relname=%s"
2466 " AND c.oid=a.attrelid"
2467 " AND a.attisdropped=%s"
2468 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2469 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2471 for column in cr.dictfetchall():
2473 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2474 column['attname'], self._table, self._name)
2475 if column['attnotnull']:
2476 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2477 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2478 self._table, column['attname'])
2480 def _auto_init(self, cr, context=None):
2483 store_compute = False
2486 self._field_create(cr, context=context)
2487 if getattr(self, '_auto', True):
2488 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2490 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2491 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2493 self.__schema.debug("Table '%s': created", self._table)
2496 if self._parent_store:
2497 cr.execute("""SELECT c.relname
2498 FROM pg_class c, pg_attribute a
2499 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2500 """, (self._table, 'parent_left'))
2502 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2503 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2504 if 'parent_left' not in self._columns:
2505 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2507 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2508 self._table, 'parent_left', 'INTEGER')
2509 elif not self._columns['parent_left'].select:
2510 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2512 if 'parent_right' not in self._columns:
2513 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2515 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2516 self._table, 'parent_right', 'INTEGER')
2517 elif not self._columns['parent_right'].select:
2518 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2520 if self._columns[self._parent_name].ondelete != 'cascade':
2521 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2522 self._parent_name, self._name)
2525 store_compute = True
2527 if self._log_access:
2529 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2530 'create_date': 'TIMESTAMP',
2531 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2532 'write_date': 'TIMESTAMP'
2537 FROM pg_class c, pg_attribute a
2538 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2539 """, (self._table, k))
2541 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2543 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2544 self._table, k, logs[k])
2546 self._check_removed_columns(cr, log=False)
2548 # iterate on the "object columns"
2549 todo_update_store = []
2550 update_custom_fields = context.get('update_custom_fields', False)
2552 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 " \
2553 "FROM pg_class c,pg_attribute a,pg_type t " \
2554 "WHERE c.relname=%s " \
2555 "AND c.oid=a.attrelid " \
2556 "AND a.atttypid=t.oid", (self._table,))
2557 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2560 for k in self._columns:
2561 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2563 #Not Updating Custom fields
2564 if k.startswith('x_') and not update_custom_fields:
2567 f = self._columns[k]
2569 if isinstance(f, fields.one2many):
2570 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2572 if self.pool.get(f._obj):
2573 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2574 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2575 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2578 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))
2579 res = cr.fetchone()[0]
2581 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2582 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2583 self._obj, f._fields_id, f._table)
2584 elif isinstance(f, fields.many2many):
2585 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2586 if not cr.dictfetchall():
2587 if not self.pool.get(f._obj):
2588 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2589 ref = self.pool.get(f._obj)._table
2590 # ref = f._obj.replace('.', '_')
2591 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))
2592 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2593 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2594 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2596 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2597 f._rel, self._table, ref)
2599 res = col_data.get(k, [])
2600 res = res and [res] or []
2601 if not res and hasattr(f, 'oldname'):
2602 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 " \
2603 "FROM pg_class c,pg_attribute a,pg_type t " \
2604 "WHERE c.relname=%s " \
2605 "AND a.attname=%s " \
2606 "AND c.oid=a.attrelid " \
2607 "AND a.atttypid=t.oid", (self._table, f.oldname))
2608 res_old = cr.dictfetchall()
2609 if res_old and len(res_old) == 1:
2610 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2612 res[0]['attname'] = k
2613 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2614 self._table, f.oldname, k)
2618 f_pg_type = f_pg_def['typname']
2619 f_pg_size = f_pg_def['size']
2620 f_pg_notnull = f_pg_def['attnotnull']
2621 if isinstance(f, fields.function) and not f.store and\
2622 not getattr(f, 'nodrop', False):
2623 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2624 k, f.string, self._table)
2625 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2627 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2631 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2636 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2637 ('varchar', 'text', 'TEXT', ''),
2638 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2639 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2640 ('timestamp', 'date', 'date', '::date'),
2641 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2642 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2644 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2645 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2646 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2647 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2648 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2650 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2651 self._table, k, f_pg_size, f.size)
2653 if (f_pg_type==c[0]) and (f._type==c[1]):
2654 if f_pg_type != f_obj_type:
2656 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2657 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2658 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2659 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2661 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2662 self._table, k, c[0], c[1])
2665 if f_pg_type != f_obj_type:
2669 newname = k + '_moved' + str(i)
2670 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2671 "WHERE c.relname=%s " \
2672 "AND a.attname=%s " \
2673 "AND c.oid=a.attrelid ", (self._table, newname))
2674 if not cr.fetchone()[0]:
2678 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2679 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2680 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2681 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2682 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2683 self._table, k, f_pg_type, f._type, newname)
2685 # if the field is required and hasn't got a NOT NULL constraint
2686 if f.required and f_pg_notnull == 0:
2687 # set the field to the default value if any
2688 if k in self._defaults:
2689 if callable(self._defaults[k]):
2690 default = self._defaults[k](self, cr, 1, context)
2692 default = self._defaults[k]
2694 if (default is not None):
2695 ss = self._columns[k]._symbol_set
2696 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2697 cr.execute(query, (ss[1](default),))
2698 # add the NOT NULL constraint
2701 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2703 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2706 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2707 "If you want to have it, you should update the records and execute manually:\n"\
2708 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2709 self.__schema.warn(msg, self._table, k, self._table, k)
2711 elif not f.required and f_pg_notnull == 1:
2712 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2714 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2717 indexname = '%s_%s_index' % (self._table, k)
2718 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2719 res2 = cr.dictfetchall()
2720 if not res2 and f.select:
2721 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2723 if f._type == 'text':
2724 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2725 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2726 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2727 " because there is a length limit for indexable btree values!\n"\
2728 "Use a search view instead if you simply want to make the field searchable."
2729 self.__schema.warn(msg, self._table, k, f._type)
2730 if res2 and not f.select:
2731 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2733 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2734 self.__schema.warn(msg, self._table, k, f._type)
2736 if isinstance(f, fields.many2one):
2737 ref = self.pool.get(f._obj)._table
2738 if ref != 'ir_actions':
2739 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2740 'pg_attribute as att1, pg_attribute as att2 '
2741 'WHERE con.conrelid = cl1.oid '
2742 'AND cl1.relname = %s '
2743 'AND con.confrelid = cl2.oid '
2744 'AND cl2.relname = %s '
2745 'AND array_lower(con.conkey, 1) = 1 '
2746 'AND con.conkey[1] = att1.attnum '
2747 'AND att1.attrelid = cl1.oid '
2748 'AND att1.attname = %s '
2749 'AND array_lower(con.confkey, 1) = 1 '
2750 'AND con.confkey[1] = att2.attnum '
2751 'AND att2.attrelid = cl2.oid '
2752 'AND att2.attname = %s '
2753 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2754 res2 = cr.dictfetchall()
2756 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2757 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2758 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2760 self.__schema.debug("Table '%s': column '%s': XXX",
2763 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2765 if not isinstance(f, fields.function) or f.store:
2766 # add the missing field
2767 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2768 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2769 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2770 self._table, k, get_pg_type(f)[1])
2773 if not create and k in self._defaults:
2774 if callable(self._defaults[k]):
2775 default = self._defaults[k](self, cr, 1, context)
2777 default = self._defaults[k]
2779 ss = self._columns[k]._symbol_set
2780 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2781 cr.execute(query, (ss[1](default),))
2783 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2785 if isinstance(f, fields.function):
2787 if f.store is not True:
2788 order = f.store[f.store.keys()[0]][2]
2789 todo_update_store.append((order, f, k))
2791 # and add constraints if needed
2792 if isinstance(f, fields.many2one):
2793 if not self.pool.get(f._obj):
2794 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2795 ref = self.pool.get(f._obj)._table
2796 # ref = f._obj.replace('.', '_')
2797 # ir_actions is inherited so foreign key doesn't work on it
2798 if ref != 'ir_actions':
2799 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2800 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2801 self._table, k, ref, f.ondelete)
2803 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2807 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
2808 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2811 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2812 "Try to re-run: openerp-server.py --update=module\n"\
2813 "If it doesn't work, update records and execute manually:\n"\
2814 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2815 self.__logger.warn(msg, k, self._table, self._table, k)
2817 for order, f, k in todo_update_store:
2818 todo_end.append((order, self._update_store, (f, k)))
2821 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2822 create = not bool(cr.fetchone())
2824 cr.commit() # start a new transaction
2826 for (key, con, _) in self._sql_constraints:
2827 conname = '%s_%s' % (self._table, key)
2829 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2830 existing_constraints = cr.dictfetchall()
2835 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2836 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2837 self._table, conname, con),
2838 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2843 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2844 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2845 '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" % (
2851 if not existing_constraints:
2852 # constraint does not exists:
2853 sql_actions['add']['execute'] = True
2854 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2855 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2856 # constraint exists but its definition has changed:
2857 sql_actions['drop']['execute'] = True
2858 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2859 sql_actions['add']['execute'] = True
2860 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2862 # we need to add the constraint:
2863 sql_actions = [item for item in sql_actions.values()]
2864 sql_actions.sort(key=lambda x: x['order'])
2865 for sql_action in [action for action in sql_actions if action['execute']]:
2867 cr.execute(sql_action['query'])
2869 self.__schema.debug(sql_action['msg_ok'])
2871 self.__schema.warn(sql_action['msg_err'])
2875 if hasattr(self, "_sql"):
2876 for line in self._sql.split(';'):
2877 line2 = line.replace('\n', '').strip()
2882 self._parent_store_compute(cr)
2886 def __init__(self, cr):
2887 super(orm, self).__init__(cr)
2889 if not hasattr(self, '_log_access'):
2890 # if not access is not specify, it is the same value as _auto
2891 self._log_access = getattr(self, "_auto", True)
2893 self._columns = self._columns.copy()
2894 for store_field in self._columns:
2895 f = self._columns[store_field]
2896 if hasattr(f, 'digits_change'):
2898 if not isinstance(f, fields.function):
2902 if self._columns[store_field].store is True:
2903 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2905 sm = self._columns[store_field].store
2906 for object, aa in sm.items():
2908 (fnct, fields2, order, length) = aa
2910 (fnct, fields2, order) = aa
2913 raise except_orm('Error',
2914 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2915 self.pool._store_function.setdefault(object, [])
2917 for x, y, z, e, f, l in self.pool._store_function[object]:
2918 if (x==self._name) and (y==store_field) and (e==fields2):
2922 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
2923 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
2925 for (key, _, msg) in self._sql_constraints:
2926 self.pool._sql_error[self._table+'_'+key] = msg
2928 # Load manual fields
2930 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
2932 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
2933 for field in cr.dictfetchall():
2934 if field['name'] in self._columns:
2937 'string': field['field_description'],
2938 'required': bool(field['required']),
2939 'readonly': bool(field['readonly']),
2940 'domain': field['domain'] or None,
2941 'size': field['size'],
2942 'ondelete': field['on_delete'],
2943 'translate': (field['translate']),
2944 #'select': int(field['select_level'])
2947 if field['ttype'] == 'selection':
2948 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
2949 elif field['ttype'] == 'reference':
2950 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
2951 elif field['ttype'] == 'many2one':
2952 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
2953 elif field['ttype'] == 'one2many':
2954 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
2955 elif field['ttype'] == 'many2many':
2956 _rel1 = field['relation'].replace('.', '_')
2957 _rel2 = field['model'].replace('.', '_')
2958 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
2959 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
2961 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
2962 self._inherits_check()
2963 self._inherits_reload()
2964 if not self._sequence:
2965 self._sequence = self._table + '_id_seq'
2966 for k in self._defaults:
2967 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,)
2968 for f in self._columns:
2969 self._columns[f].restart()
2972 # Update objects that uses this one to update their _inherits fields
2975 def _inherits_reload_src(self):
2976 for obj in self.pool.obj_pool.values():
2977 if self._name in obj._inherits:
2978 obj._inherits_reload()
2980 def _inherits_reload(self):
2982 for table in self._inherits:
2983 res.update(self.pool.get(table)._inherit_fields)
2984 for col in self.pool.get(table)._columns.keys():
2985 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
2986 for col in self.pool.get(table)._inherit_fields.keys():
2987 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
2988 self._inherit_fields = res
2989 self._inherits_reload_src()
2991 def _inherits_check(self):
2992 for table, field_name in self._inherits.items():
2993 if field_name not in self._columns:
2994 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
2995 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2996 required=True, ondelete="cascade")
2997 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
2998 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))
2999 self._columns[field_name].required = True
3000 self._columns[field_name].ondelete = "cascade"
3002 #def __getattr__(self, name):
3004 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
3005 # (though inherits doesn't use Python inheritance).
3006 # Handles translating between local ids and remote ids.
3007 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
3008 # when you have inherits.
3010 # for model, field in self._inherits.iteritems():
3011 # proxy = self.pool.get(model)
3012 # if hasattr(proxy, name):
3013 # attribute = getattr(proxy, name)
3014 # if not hasattr(attribute, '__call__'):
3018 # return super(orm, self).__getattr__(name)
3020 # def _proxy(cr, uid, ids, *args, **kwargs):
3021 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
3022 # lst = [obj[field].id for obj in objects if obj[field]]
3023 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
3028 def fields_get(self, cr, user, fields=None, context=None):
3030 Get the description of list of fields
3032 :param cr: database cursor
3033 :param user: current user id
3034 :param fields: list of fields
3035 :param context: context arguments, like lang, time zone
3036 :return: dictionary of field dictionaries, each one describing a field of the business object
3037 :raise AccessError: * if user has no create/write rights on the requested object
3040 ira = self.pool.get('ir.model.access')
3041 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
3042 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
3043 return super(orm, self).fields_get(cr, user, fields, context, write_access)
3045 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
3048 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3050 fields = self._columns.keys() + self._inherit_fields.keys()
3051 if isinstance(ids, (int, long)):
3055 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
3056 result = self._read_flat(cr, user, select, fields, context, load)
3059 for key, v in r.items():
3063 if isinstance(ids, (int, long, dict)):
3064 return result and result[0] or False
3067 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
3072 if fields_to_read == None:
3073 fields_to_read = self._columns.keys()
3075 # Construct a clause for the security rules.
3076 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3077 # or will at least contain self._table.
3078 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3080 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
3081 fields_pre = [f for f in fields_to_read if
3082 f == self.CONCURRENCY_CHECK_FIELD
3083 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3084 ] + self._inherits.values()
3088 def convert_field(f):
3089 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
3090 if f in ('create_date', 'write_date'):
3091 return "date_trunc('second', %s) as %s" % (f_qual, f)
3092 if f == self.CONCURRENCY_CHECK_FIELD:
3093 if self._log_access:
3094 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
3095 return "now()::timestamp AS %s" % (f,)
3096 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
3097 return 'length(%s) as "%s"' % (f_qual, f)
3100 fields_pre2 = map(convert_field, fields_pre)
3101 order_by = self._parent_order or self._order
3102 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
3103 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3105 query += " AND " + (' OR '.join(rule_clause))
3106 query += " ORDER BY " + order_by
3107 for sub_ids in cr.split_for_in_conditions(ids):
3109 cr.execute(query, [tuple(sub_ids)] + rule_params)
3110 if cr.rowcount != len(sub_ids):
3111 raise except_orm(_('AccessError'),
3112 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3113 % (self._description,))
3115 cr.execute(query, (tuple(sub_ids),))
3116 res.extend(cr.dictfetchall())
3118 res = map(lambda x: {'id': x}, ids)
3120 for f in fields_pre:
3121 if f == self.CONCURRENCY_CHECK_FIELD:
3123 if self._columns[f].translate:
3124 ids = [x['id'] for x in res]
3125 #TODO: optimize out of this loop
3126 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3128 r[f] = res_trans.get(r['id'], False) or r[f]
3130 for table in self._inherits:
3131 col = self._inherits[table]
3132 cols = intersect(self._inherit_fields.keys(), set(fields_to_read) - set(self._columns.keys()))
3135 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3143 if not record[col]: # if the record is deleted from _inherits table?
3145 record.update(res3[record[col]])
3146 if col not in fields_to_read:
3149 # all fields which need to be post-processed by a simple function (symbol_get)
3150 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3153 for f in fields_post:
3154 r[f] = self._columns[f]._symbol_get(r[f])
3155 ids = [x['id'] for x in res]
3157 # all non inherited fields for which the attribute whose name is in load is False
3158 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3160 # Compute POST fields
3162 for f in fields_post:
3163 todo.setdefault(self._columns[f]._multi, [])
3164 todo[self._columns[f]._multi].append(f)
3165 for key, val in todo.items():
3167 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3170 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3171 multi_fields = res2.get(record['id'],{})
3173 record[pos] = multi_fields.get(pos,[])
3176 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3179 record[f] = res2[record['id']]
3184 for field in vals.copy():
3186 if field in self._columns:
3187 fobj = self._columns[field]
3194 for group in groups:
3195 module = group.split(".")[0]
3196 grp = group.split(".")[1]
3197 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" \
3198 (grp, module, 'res.groups', user))
3199 readonly = cr.fetchall()
3200 if readonly[0][0] >= 1:
3203 elif readonly[0][0] == 0:
3209 if type(vals[field]) == type([]):
3211 elif type(vals[field]) == type(0.0):
3213 elif type(vals[field]) == type(''):
3214 vals[field] = '=No Permission='
3219 def perm_read(self, cr, user, ids, context=None, details=True):
3221 Returns some metadata about the given records.
3223 :param details: if True, \*_uid fields are replaced with the name of the user
3224 :return: list of ownership dictionaries for each requested record
3225 :rtype: list of dictionaries with the following keys:
3228 * create_uid: user who created the record
3229 * create_date: date when the record was created
3230 * write_uid: last user who changed the record
3231 * write_date: date of the last change to the record
3232 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3239 uniq = isinstance(ids, (int, long))
3243 if self._log_access:
3244 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3245 quoted_table = '"%s"' % self._table
3246 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3247 query = '''SELECT %s, __imd.module, __imd.name
3248 FROM %s LEFT JOIN ir_model_data __imd
3249 ON (__imd.model = %%s and __imd.res_id = %s.id)
3250 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3251 cr.execute(query, (self._name, tuple(ids)))
3252 res = cr.dictfetchall()
3255 r[key] = r[key] or False
3256 if details and key in ('write_uid', 'create_uid') and r[key]:
3258 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3260 pass # Leave the numeric uid there
3261 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3262 del r['name'], r['module']
3267 def _check_concurrency(self, cr, ids, context):
3270 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3272 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3273 for sub_ids in cr.split_for_in_conditions(ids):
3276 id_ref = "%s,%s" % (self._name, id)
3277 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3279 ids_to_check.extend([id, update_date])
3280 if not ids_to_check:
3282 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3285 # mention the first one only to keep the error message readable
3286 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3288 def check_access_rule(self, cr, uid, ids, operation, context=None):
3289 """Verifies that the operation given by ``operation`` is allowed for the user
3290 according to ir.rules.
3292 :param operation: one of ``write``, ``unlink``
3293 :raise except_orm: * if current ir.rules do not permit this operation.
3294 :return: None if the operation is allowed
3296 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3298 where_clause = ' and ' + ' and '.join(where_clause)
3299 for sub_ids in cr.split_for_in_conditions(ids):
3300 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3301 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3302 [sub_ids] + where_params)
3303 if cr.rowcount != len(sub_ids):
3304 raise except_orm(_('AccessError'),
3305 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3306 % (operation, self._description))
3308 def unlink(self, cr, uid, ids, context=None):
3310 Delete records with given ids
3312 :param cr: database cursor
3313 :param uid: current user id
3314 :param ids: id or list of ids
3315 :param context: (optional) context arguments, like lang, time zone
3317 :raise AccessError: * if user has no unlink rights on the requested object
3318 * if user tries to bypass access rules for unlink on the requested object
3319 :raise UserError: if the record is default property for other records
3324 if isinstance(ids, (int, long)):
3327 result_store = self._store_get_values(cr, uid, ids, None, context)
3329 self._check_concurrency(cr, ids, context)
3331 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3333 properties = self.pool.get('ir.property')
3334 domain = [('res_id', '=', False),
3335 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3337 if properties.search(cr, uid, domain, context=context):
3338 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3340 wf_service = netsvc.LocalService("workflow")
3342 wf_service.trg_delete(uid, self._name, oid, cr)
3345 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3346 for sub_ids in cr.split_for_in_conditions(ids):
3347 cr.execute('delete from ' + self._table + ' ' \
3348 'where id IN %s', (sub_ids,))
3349 for order, object, store_ids, fields in result_store:
3350 if object != self._name:
3351 obj = self.pool.get(object)
3352 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3353 rids = map(lambda x: x[0], cr.fetchall())
3355 obj._store_set_values(cr, uid, rids, fields, context)
3361 def write(self, cr, user, ids, vals, context=None):
3363 Update records with given ids with the given field values
3365 :param cr: database cursor
3366 :param user: current user id
3368 :param ids: object id or list of object ids to update according to **vals**
3369 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3370 :type vals: dictionary
3371 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3372 :type context: dictionary
3374 :raise AccessError: * if user has no write rights on the requested object
3375 * if user tries to bypass access rules for write on the requested object
3376 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3377 :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)
3379 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3381 + For a many2many field, a list of tuples is expected.
3382 Here is the list of tuple that are accepted, with the corresponding semantics ::
3384 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3385 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3386 (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)
3387 (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)
3388 (4, ID) link to existing record with id = ID (adds a relationship)
3389 (5) unlink all (like using (3,ID) for all linked records)
3390 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3393 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3395 + For a one2many field, a lits of tuples is expected.
3396 Here is the list of tuple that are accepted, with the corresponding semantics ::
3398 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3399 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3400 (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)
3403 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3405 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3406 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3410 for field in vals.copy():
3412 if field in self._columns:
3413 fobj = self._columns[field]
3414 elif field in self._inherit_fields:
3415 fobj = self._inherit_fields[field][2]
3422 for group in groups:
3423 module = group.split(".")[0]
3424 grp = group.split(".")[1]
3425 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", \
3426 (grp, module, 'res.groups', user))
3427 readonly = cr.fetchall()
3428 if readonly[0][0] >= 1:
3431 elif readonly[0][0] == 0:
3443 if isinstance(ids, (int, long)):
3446 self._check_concurrency(cr, ids, context)
3447 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3449 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3451 # No direct update of parent_left/right
3452 vals.pop('parent_left', None)
3453 vals.pop('parent_right', None)
3455 parents_changed = []
3456 if self._parent_store and (self._parent_name in vals):
3457 # The parent_left/right computation may take up to
3458 # 5 seconds. No need to recompute the values if the
3459 # parent is the same. Get the current value of the parent
3460 parent_val = vals[self._parent_name]
3462 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL)" % \
3463 (self._table, self._parent_name, self._parent_name)
3464 cr.execute(query, (tuple(ids), parent_val))
3466 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL)" % \
3467 (self._table, self._parent_name)
3468 cr.execute(query, (tuple(ids),))
3469 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3476 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3478 if field in self._columns:
3479 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3480 if (not totranslate) or not self._columns[field].translate:
3481 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3482 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3483 direct.append(field)
3485 upd_todo.append(field)
3487 updend.append(field)
3488 if field in self._columns \
3489 and hasattr(self._columns[field], 'selection') \
3491 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3493 if self._log_access:
3494 upd0.append('write_uid=%s')
3495 upd0.append('write_date=now()')
3499 self.check_access_rule(cr, user, ids, 'write', context=context)
3500 for sub_ids in cr.split_for_in_conditions(ids):
3501 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3502 'where id IN %s', upd1 + [sub_ids])
3503 if cr.rowcount != len(sub_ids):
3504 raise except_orm(_('AccessError'),
3505 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3510 if self._columns[f].translate:
3511 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3514 # Inserting value to DB
3515 self.write(cr, user, ids, {f: vals[f]})
3516 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3519 # call the 'set' method of fields which are not classic_write
3520 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3522 # default element in context must be removed when call a one2many or many2many
3523 rel_context = context.copy()
3524 for c in context.items():
3525 if c[0].startswith('default_'):
3526 del rel_context[c[0]]
3528 for field in upd_todo:
3530 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3532 for table in self._inherits:
3533 col = self._inherits[table]
3535 for sub_ids in cr.split_for_in_conditions(ids):
3536 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3537 'where id IN %s', (sub_ids,))
3538 nids.extend([x[0] for x in cr.fetchall()])
3542 if self._inherit_fields[val][0] == table:
3545 self.pool.get(table).write(cr, user, nids, v, context)
3547 self._validate(cr, user, ids, context)
3549 # TODO: use _order to set dest at the right position and not first node of parent
3550 # We can't defer parent_store computation because the stored function
3551 # fields that are computer may refer (directly or indirectly) to
3552 # parent_left/right (via a child_of domain)
3555 self.pool._init_parent[self._name] = True
3557 order = self._parent_order or self._order
3558 parent_val = vals[self._parent_name]
3560 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3562 clause, params = '%s IS NULL' % (self._parent_name,), ()
3563 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, order), params)
3564 parents = cr.fetchall()
3566 for id in parents_changed:
3567 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3568 pleft, pright = cr.fetchone()
3569 distance = pright - pleft + 1
3571 # Find Position of the element
3573 for (parent_pright, parent_id) in parents:
3576 position = parent_pright + 1
3578 # It's the first node of the parent
3583 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3584 position = cr.fetchone()[0] + 1
3586 if pleft < position <= pright:
3587 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3589 if pleft < position:
3590 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3591 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3592 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))
3594 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3595 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3596 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))
3598 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3602 for order, object, ids, fields in result:
3603 key = (object, tuple(fields))
3604 done.setdefault(key, {})
3605 # avoid to do several times the same computation
3608 if id not in done[key]:
3609 done[key][id] = True
3611 self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
3613 wf_service = netsvc.LocalService("workflow")
3615 wf_service.trg_write(user, self._name, id, cr)
3619 # TODO: Should set perm to user.xxx
3621 def create(self, cr, user, vals, context=None):
3623 Create new record with specified value
3625 :param cr: database cursor
3626 :param user: current user id
3628 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3629 :type vals: dictionary
3630 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3631 :type context: dictionary
3632 :return: id of new record created
3633 :raise AccessError: * if user has no create rights on the requested object
3634 * if user tries to bypass access rules for create on the requested object
3635 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3636 :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)
3638 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3639 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3645 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3647 vals = self._add_missing_default_values(cr, user, vals, context)
3650 for v in self._inherits:
3651 if self._inherits[v] not in vals:
3654 tocreate[v] = {'id': vals[self._inherits[v]]}
3655 (upd0, upd1, upd2) = ('', '', [])
3657 for v in vals.keys():
3658 if v in self._inherit_fields:
3659 (table, col, col_detail) = self._inherit_fields[v]
3660 tocreate[table][v] = vals[v]
3663 if (v not in self._inherit_fields) and (v not in self._columns):
3666 # Try-except added to filter the creation of those records whose filds are readonly.
3667 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3669 cr.execute("SELECT nextval('"+self._sequence+"')")
3671 raise except_orm(_('UserError'),
3672 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3674 id_new = cr.fetchone()[0]
3675 for table in tocreate:
3676 if self._inherits[table] in vals:
3677 del vals[self._inherits[table]]
3679 record_id = tocreate[table].pop('id', None)
3681 if record_id is None or not record_id:
3682 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3684 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3686 upd0 += ',' + self._inherits[table]
3688 upd2.append(record_id)
3690 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3691 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3693 for bool_field in bool_fields:
3694 if bool_field not in vals:
3695 vals[bool_field] = False
3697 for field in vals.copy():
3699 if field in self._columns:
3700 fobj = self._columns[field]
3702 fobj = self._inherit_fields[field][2]
3708 for group in groups:
3709 module = group.split(".")[0]
3710 grp = group.split(".")[1]
3711 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" % \
3712 (grp, module, 'res.groups', user))
3713 readonly = cr.fetchall()
3714 if readonly[0][0] >= 1:
3717 elif readonly[0][0] == 0:
3725 if self._columns[field]._classic_write:
3726 upd0 = upd0 + ',"' + field + '"'
3727 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3728 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3730 if not isinstance(self._columns[field], fields.related):
3731 upd_todo.append(field)
3732 if field in self._columns \
3733 and hasattr(self._columns[field], 'selection') \
3735 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3736 if self._log_access:
3737 upd0 += ',create_uid,create_date'
3740 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3741 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3742 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3744 if self._parent_store and not context.get('defer_parent_store_computation'):
3746 self.pool._init_parent[self._name] = True
3748 parent = vals.get(self._parent_name, False)
3750 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3752 result_p = cr.fetchall()
3753 for (pleft,) in result_p:
3758 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3759 pleft_old = cr.fetchone()[0]
3762 cr.execute('select max(parent_right) from '+self._table)
3763 pleft = cr.fetchone()[0] or 0
3764 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3765 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3766 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3768 # default element in context must be remove when call a one2many or many2many
3769 rel_context = context.copy()
3770 for c in context.items():
3771 if c[0].startswith('default_'):
3772 del rel_context[c[0]]
3775 for field in upd_todo:
3776 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3777 self._validate(cr, user, [id_new], context)
3779 if not context.get('no_store_function', False):
3780 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3783 for order, object, ids, fields2 in result:
3784 if not (object, ids, fields2) in done:
3785 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3786 done.append((object, ids, fields2))
3788 if self._log_create and not (context and context.get('no_store_function', False)):
3789 message = self._description + \
3791 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3792 "' " + _("created.")
3793 self.log(cr, user, id_new, message, True, context=context)
3794 wf_service = netsvc.LocalService("workflow")
3795 wf_service.trg_create(user, self._name, id_new, cr)
3798 def _store_get_values(self, cr, uid, ids, fields, context):
3800 fncts = self.pool._store_function.get(self._name, [])
3801 for fnct in range(len(fncts)):
3806 for f in (fields or []):
3807 if f in fncts[fnct][3]:
3813 result.setdefault(fncts[fnct][0], {})
3815 # uid == 1 for accessing objects having rules defined on store fields
3816 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3817 for id in filter(None, ids2):
3818 result[fncts[fnct][0]].setdefault(id, [])
3819 result[fncts[fnct][0]][id].append(fnct)
3821 for object in result:
3823 for id, fnct in result[object].items():
3824 k2.setdefault(tuple(fnct), [])
3825 k2[tuple(fnct)].append(id)
3826 for fnct, id in k2.items():
3827 dict.setdefault(fncts[fnct[0]][4], [])
3828 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3836 def _store_set_values(self, cr, uid, ids, fields, context):
3841 if self._log_access:
3842 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3846 field_dict.setdefault(r[0], [])
3847 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3848 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3849 for i in self.pool._store_function.get(self._name, []):
3851 up_write_date = write_date + datetime.timedelta(hours=i[5])
3852 if datetime.datetime.now() < up_write_date:
3854 field_dict[r[0]].append(i[1])
3860 if self._columns[f]._multi not in keys:
3861 keys.append(self._columns[f]._multi)
3862 todo.setdefault(self._columns[f]._multi, [])
3863 todo[self._columns[f]._multi].append(f)
3867 # uid == 1 for accessing objects having rules defined on store fields
3868 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3869 for id, value in result.items():
3871 for f in value.keys():
3872 if f in field_dict[id]:
3879 if self._columns[v]._type in ('many2one', 'one2one'):
3881 value[v] = value[v][0]
3884 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
3885 upd1.append(self._columns[v]._symbol_set[1](value[v]))
3888 cr.execute('update "' + self._table + '" set ' + \
3889 ','.join(upd0) + ' where id = %s', upd1)
3893 # uid == 1 for accessing objects having rules defined on store fields
3894 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
3895 for r in result.keys():
3897 if r in field_dict.keys():
3898 if f in field_dict[r]:
3900 for id, value in result.items():
3901 if self._columns[f]._type in ('many2one', 'one2one'):
3906 cr.execute('update "' + self._table + '" set ' + \
3907 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
3913 def perm_write(self, cr, user, ids, fields, context=None):
3914 raise NotImplementedError(_('This method does not exist anymore'))
3916 # TODO: ameliorer avec NULL
3917 def _where_calc(self, cr, user, domain, active_test=True, context=None):
3918 """Computes the WHERE clause needed to implement an OpenERP domain.
3919 :param domain: the domain to compute
3921 :param active_test: whether the default filtering of records with ``active``
3922 field set to ``False`` should be applied.
3923 :return: the query expressing the given domain as provided in domain
3924 :rtype: osv.query.Query
3929 # if the object has a field named 'active', filter out all inactive
3930 # records unless they were explicitely asked for
3931 if 'active' in self._columns and (active_test and context.get('active_test', True)):
3933 active_in_args = False
3935 if a[0] == 'active':
3936 active_in_args = True
3937 if not active_in_args:
3938 domain.insert(0, ('active', '=', 1))
3940 domain = [('active', '=', 1)]
3944 e = expression.expression(domain)
3945 e.parse(cr, user, self, context)
3946 tables = e.get_tables()
3947 where_clause, where_params = e.to_sql()
3948 where_clause = where_clause and [where_clause] or []
3950 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
3952 return Query(tables, where_clause, where_params)
3954 def _check_qorder(self, word):
3955 if not regex_order.match(word):
3956 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)'))
3959 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
3960 """Add what's missing in ``query`` to implement all appropriate ir.rules
3961 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
3963 :param query: the current query object
3965 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
3967 if parent_model and child_object:
3968 # as inherited rules are being applied, we need to add the missing JOIN
3969 # to reach the parent table (if it was not JOINed yet in the query)
3970 child_object._inherits_join_add(parent_model, query)
3971 query.where_clause += added_clause
3972 query.where_clause_params += added_params
3973 for table in added_tables:
3974 if table not in query.tables:
3975 query.tables.append(table)
3979 # apply main rules on the object
3980 rule_obj = self.pool.get('ir.rule')
3981 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
3983 # apply ir.rules from the parents (through _inherits)
3984 for inherited_model in self._inherits:
3985 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
3986 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
3988 def _generate_m2o_order_by(self, order_field, query):
3990 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
3991 either native m2o fields or function/related fields that are stored, including
3992 intermediate JOINs for inheritance if required.
3994 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
3996 if order_field not in self._columns and order_field in self._inherit_fields:
3997 # also add missing joins for reaching the table containing the m2o field
3998 qualified_field = self._inherits_join_calc(order_field, query)
3999 order_field_column = self._inherit_fields[order_field][2]
4001 qualified_field = '"%s"."%s"' % (self._table, order_field)
4002 order_field_column = self._columns[order_field]
4004 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
4005 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
4006 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
4007 "to be used as ordering fields! Ignoring sorting for %s.%s",
4008 self._name, order_field)
4011 # figure out the applicable order_by for the m2o
4012 dest_model = self.pool.get(order_field_column._obj)
4013 m2o_order = dest_model._order
4014 if not regex_order.match(m2o_order):
4015 # _order is complex, can't use it here, so we default to _rec_name
4016 m2o_order = dest_model._rec_name
4018 # extract the first field name, to be able to qualify it and add desc/asc
4019 m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
4021 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
4022 # as we don't want to exclude results that have NULL values for the m2o
4023 src_table, src_field = qualified_field.replace('"','').split('.', 1)
4024 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
4025 return '"%s"."%s"' % (dest_model._table, m2o_order)
4028 def _generate_order_by(self, order_spec, query):
4030 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
4031 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
4033 :raise" except_orm in case order_spec is malformed
4035 order_by_clause = self._order
4037 order_by_elements = []
4038 self._check_qorder(order_spec)
4039 for order_part in order_spec.split(','):
4040 order_split = order_part.strip().split(' ')
4041 order_field = order_split[0].strip()
4042 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
4044 if order_field in self._columns:
4045 order_column = self._columns[order_field]
4046 if order_column._classic_read:
4047 inner_clause = '"%s"."%s"' % (self._table, order_field)
4048 elif order_column._type == 'many2one':
4049 inner_clause = self._generate_m2o_order_by(order_field, query)
4051 continue # ignore non-readable or "non-joignable" fields
4052 elif order_field in self._inherit_fields:
4053 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4054 order_column = parent_obj._columns[order_field]
4055 if order_column._classic_read:
4056 inner_clause = self._inherits_join_calc(order_field, query)
4057 elif order_column._type == 'many2one':
4058 inner_clause = self._generate_m2o_order_by(order_field, query)
4060 continue # ignore non-readable or "non-joignable" fields
4062 order_by_elements.append("%s %s" % (inner_clause, order_direction))
4063 if order_by_elements:
4064 order_by_clause = ",".join(order_by_elements)
4066 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4068 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4070 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4071 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4072 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4073 This is ok at the security level because this method is private and not callable through XML-RPC.
4075 :param access_rights_uid: optional user ID to use when checking access rights
4076 (not for ir.rules, this is only for ir.model.access)
4080 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4082 query = self._where_calc(cr, user, args, context=context)
4083 self._apply_ir_rules(cr, user, query, 'read', context=context)
4084 order_by = self._generate_order_by(order, query)
4085 from_clause, where_clause, where_clause_params = query.get_sql()
4087 limit_str = limit and ' limit %d' % limit or ''
4088 offset_str = offset and ' offset %d' % offset or ''
4089 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4092 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4095 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4097 return [x[0] for x in res]
4099 # returns the different values ever entered for one field
4100 # this is used, for example, in the client when the user hits enter on
4102 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4105 if field in self._inherit_fields:
4106 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4108 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4110 def copy_data(self, cr, uid, id, default=None, context=None):
4112 Copy given record's data with all its fields values
4114 :param cr: database cursor
4115 :param user: current user id
4116 :param id: id of the record to copy
4117 :param default: field values to override in the original values of the copied record
4118 :type default: dictionary
4119 :param context: context arguments, like lang, time zone
4120 :type context: dictionary
4121 :return: dictionary containing all the field values
4127 # avoid recursion through already copied records in case of circular relationship
4128 seen_map = context.setdefault('__copy_data_seen',{})
4129 if id in seen_map.setdefault(self._name,[]):
4131 seen_map[self._name].append(id)
4135 if 'state' not in default:
4136 if 'state' in self._defaults:
4137 if callable(self._defaults['state']):
4138 default['state'] = self._defaults['state'](self, cr, uid, context)
4140 default['state'] = self._defaults['state']
4142 context_wo_lang = context.copy()
4143 if 'lang' in context:
4144 del context_wo_lang['lang']
4145 data = self.read(cr, uid, [id,], context=context_wo_lang)
4149 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4151 fields = self.fields_get(cr, uid, context=context)
4153 ftype = fields[f]['type']
4155 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4159 data[f] = default[f]
4160 elif 'function' in fields[f]:
4162 elif ftype == 'many2one':
4164 data[f] = data[f] and data[f][0]
4167 elif ftype in ('one2many', 'one2one'):
4169 rel = self.pool.get(fields[f]['relation'])
4171 # duplicate following the order of the ids
4172 # because we'll rely on it later for copying
4173 # translations in copy_translation()!
4175 for rel_id in data[f]:
4176 # the lines are first duplicated using the wrong (old)
4177 # parent but then are reassigned to the correct one thanks
4178 # to the (0, 0, ...)
4179 d = rel.copy_data(cr, uid, rel_id, context=context)
4181 res.append((0, 0, d))
4183 elif ftype == 'many2many':
4184 data[f] = [(6, 0, data[f])]
4188 # make sure we don't break the current parent_store structure and
4189 # force a clean recompute!
4190 for parent_column in ['parent_left', 'parent_right']:
4191 data.pop(parent_column, None)
4193 for v in self._inherits:
4194 del data[self._inherits[v]]
4197 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4201 # avoid recursion through already copied records in case of circular relationship
4202 seen_map = context.setdefault('__copy_translations_seen',{})
4203 if old_id in seen_map.setdefault(self._name,[]):
4205 seen_map[self._name].append(old_id)
4207 trans_obj = self.pool.get('ir.translation')
4208 fields = self.fields_get(cr, uid, context=context)
4210 translation_records = []
4211 for field_name, field_def in fields.items():
4212 # we must recursively copy the translations for o2o and o2m
4213 if field_def['type'] in ('one2one', 'one2many'):
4214 target_obj = self.pool.get(field_def['relation'])
4215 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4216 # here we rely on the order of the ids to match the translations
4217 # as foreseen in copy_data()
4218 old_children = sorted(old_record[field_name])
4219 new_children = sorted(new_record[field_name])
4220 for (old_child, new_child) in zip(old_children, new_children):
4221 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4222 # and for translatable fields we keep them for copy
4223 elif field_def.get('translate'):
4225 if field_name in self._columns:
4226 trans_name = self._name + "," + field_name
4227 elif field_name in self._inherit_fields:
4228 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4230 trans_ids = trans_obj.search(cr, uid, [
4231 ('name', '=', trans_name),
4232 ('res_id', '=', old_id)
4234 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4236 for record in translation_records:
4238 record['res_id'] = new_id
4239 trans_obj.create(cr, uid, record, context=context)
4242 def copy(self, cr, uid, id, default=None, context=None):
4244 Duplicate record with given id updating it with default values
4246 :param cr: database cursor
4247 :param uid: current user id
4248 :param id: id of the record to copy
4249 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4250 :type default: dictionary
4251 :param context: context arguments, like lang, time zone
4252 :type context: dictionary
4258 context = context.copy()
4259 data = self.copy_data(cr, uid, id, default, context)
4260 new_id = self.create(cr, uid, data, context)
4261 self.copy_translations(cr, uid, id, new_id, context)
4264 def exists(self, cr, uid, ids, context=None):
4265 if type(ids) in (int, long):
4267 query = 'SELECT count(1) FROM "%s"' % (self._table)
4268 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4269 return cr.fetchone()[0] == len(ids)
4271 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4272 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4273 self._name, DeprecationWarning, stacklevel=3)
4274 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4275 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4276 return self._check_recursion(cr, uid, ids, context, parent)
4278 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4280 Verifies that there is no loop in a hierarchical structure of records,
4281 by following the parent relationship using the **parent** field until a loop
4282 is detected or until a top-level record is found.
4284 :param cr: database cursor
4285 :param uid: current user id
4286 :param ids: list of ids of records to check
4287 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4288 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4292 parent = self._parent_name
4294 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4297 for i in range(0, len(ids), cr.IN_MAX):
4298 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4299 cr.execute(query, (tuple(sub_ids_parent),))
4300 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4301 ids_parent = ids_parent2
4302 for i in ids_parent:
4307 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4308 """Find out the XML ID of any database record, if there
4309 is one. This method works as a possible implementation
4310 for a function field, to be able to add it to any
4311 model object easily, referencing it as ``osv.osv.get_xml_id``.
4313 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4315 :return: the fully qualified XML ID of the given object,
4316 defaulting to an empty string when there's none
4317 (to be usable as a function field).
4319 result = dict.fromkeys(ids, '')
4320 model_data_obj = self.pool.get('ir.model.data')
4321 data_ids = model_data_obj.search(cr, uid,
4322 [('model', '=', self._name), ('res_id', 'in', ids)])
4323 data_results = model_data_obj.read(cr, uid, data_ids,
4324 ['name', 'module', 'res_id'])
4325 for record in data_results:
4326 result[record['res_id']] = '%(module)s.%(name)s' % record
4329 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: