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)
52 import openerp.netsvc as netsvc
53 from lxml import etree
54 from openerp.tools.config import config
55 from openerp.tools.translate import _
58 from query import Query
59 import openerp.tools as tools
60 from openerp.tools.safe_eval import safe_eval as eval
62 # List of etree._Element subclasses that we choose to ignore when parsing XML.
63 from openerp.tools import SKIPPED_ELEMENT_TYPES
65 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
66 regex_object_name = re.compile(r'^[a-z0-9_.]+$')
68 # Mapping between openerp module names and their osv classes.
69 module_class_list = {}
71 def check_object_name(name):
72 """ Check if the given name is a valid openerp object name.
74 The _name attribute in osv and osv_memory object is subject to
75 some restrictions. This function returns True or False whether
76 the given name is allowed or not.
78 TODO: this is an approximation. The goal in this approximation
79 is to disallow uppercase characters (in some places, we quote
80 table/column names and in other not, which leads to this kind
83 psycopg2.ProgrammingError: relation "xxx" does not exist).
85 The same restriction should apply to both osv and osv_memory
86 objects for consistency.
89 if regex_object_name.match(name) is None:
93 def raise_on_invalid_object_name(name):
94 if not check_object_name(name):
95 msg = "The _name attribute %s is not valid." % name
96 logger = netsvc.Logger()
97 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
98 raise except_orm('ValueError', msg)
100 POSTGRES_CONFDELTYPES = {
108 def last_day_of_current_month():
109 today = datetime.date.today()
110 last_day = str(calendar.monthrange(today.year, today.month)[1])
111 return time.strftime('%Y-%m-' + last_day)
113 def intersect(la, lb):
114 return filter(lambda x: x in lb, la)
116 class except_orm(Exception):
117 def __init__(self, name, value):
120 self.args = (name, value)
122 class BrowseRecordError(Exception):
125 # Readonly python database object browser
126 class browse_null(object):
131 def __getitem__(self, name):
134 def __getattr__(self, name):
135 return None # XXX: return self ?
143 def __nonzero__(self):
146 def __unicode__(self):
151 # TODO: execute an object method on browse_record_list
153 class browse_record_list(list):
155 def __init__(self, lst, context=None):
158 super(browse_record_list, self).__init__(lst)
159 self.context = context
162 class browse_record(object):
163 logger = netsvc.Logger()
165 def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
167 table : the object (inherited from orm)
168 context : dictionary with an optional context
170 if fields_process is None:
174 self._list_class = list_class or browse_record_list
179 self._table_name = self._table._name
180 self.__logger = logging.getLogger(
181 'osv.browse_record.' + self._table_name)
182 self._context = context
183 self._fields_process = fields_process
185 cache.setdefault(table._name, {})
186 self._data = cache[table._name]
188 if not (id and isinstance(id, (int, long,))):
189 raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
190 # if not table.exists(cr, uid, id, context):
191 # raise BrowseRecordError(_('Object %s does not exists') % (self,))
193 if id not in self._data:
194 self._data[id] = {'id': id}
198 def __getitem__(self, name):
202 if name not in self._data[self._id]:
203 # build the list of fields we will fetch
205 # fetch the definition of the field which was asked for
206 if name in self._table._columns:
207 col = self._table._columns[name]
208 elif name in self._table._inherit_fields:
209 col = self._table._inherit_fields[name][2]
210 elif hasattr(self._table, str(name)):
211 attr = getattr(self._table, name)
213 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
214 return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
218 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
219 "Field '%s' does not exist in object '%s': \n%s" % (
220 name, self, ''.join(traceback.format_exc())))
221 raise KeyError("Field '%s' does not exist in object '%s'" % (
224 # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
226 # gen the list of "local" (ie not inherited) fields which are classic or many2one
227 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
228 # gen the list of inherited fields
229 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
230 # complete the field list with the inherited fields which are classic or many2one
231 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
232 # otherwise we fetch only that field
234 fields_to_fetch = [(name, col)]
235 ids = filter(lambda id: name not in self._data[id], self._data.keys())
237 field_names = map(lambda x: x[0], fields_to_fetch)
238 field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
240 # TODO: improve this, very slow for reports
241 if self._fields_process:
242 lang = self._context.get('lang', 'en_US') or 'en_US'
243 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
245 raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
246 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
248 for field_name, field_column in fields_to_fetch:
249 if field_column._type in self._fields_process:
250 for result_line in field_values:
251 result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
252 if result_line[field_name]:
253 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
256 # Where did those ids come from? Perhaps old entries in ir_model_dat?
257 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
258 raise KeyError('Field %s not found in %s'%(name, self))
259 # create browse records for 'remote' objects
260 for result_line in field_values:
262 for field_name, field_column in fields_to_fetch:
263 if field_column._type in ('many2one', 'one2one'):
264 if result_line[field_name]:
265 obj = self._table.pool.get(field_column._obj)
266 if isinstance(result_line[field_name], (list, tuple)):
267 value = result_line[field_name][0]
269 value = result_line[field_name]
271 # FIXME: this happen when a _inherits object
272 # overwrite a field of it parent. Need
273 # testing to be sure we got the right
274 # object and not the parent one.
275 if not isinstance(value, browse_record):
277 # In some cases the target model is not available yet, so we must ignore it,
278 # which is safe in most cases, this value will just be loaded later when needed.
279 # This situation can be caused by custom fields that connect objects with m2o without
280 # respecting module dependencies, causing relationships to be connected to soon when
281 # the target is not loaded yet.
283 new_data[field_name] = browse_record(self._cr,
284 self._uid, value, obj, self._cache,
285 context=self._context,
286 list_class=self._list_class,
287 fields_process=self._fields_process)
289 new_data[field_name] = value
291 new_data[field_name] = browse_null()
293 new_data[field_name] = browse_null()
294 elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
295 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)
296 elif field_column._type in ('reference'):
297 if result_line[field_name]:
298 if isinstance(result_line[field_name], browse_record):
299 new_data[field_name] = result_line[field_name]
301 ref_obj, ref_id = result_line[field_name].split(',')
302 ref_id = long(ref_id)
304 obj = self._table.pool.get(ref_obj)
305 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)
307 new_data[field_name] = browse_null()
309 new_data[field_name] = browse_null()
311 new_data[field_name] = result_line[field_name]
312 self._data[result_line['id']].update(new_data)
314 if not name in self._data[self._id]:
315 # How did this happen? Could be a missing model due to custom fields used too soon, see above.
316 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
317 "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
318 self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
319 "Cached: %s, Table: %s"%(self._data[self._id], self._table))
320 raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
321 return self._data[self._id][name]
323 def __getattr__(self, name):
327 raise AttributeError(e)
329 def __contains__(self, name):
330 return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
332 def __hasattr__(self, name):
339 return "browse_record(%s, %d)" % (self._table_name, self._id)
341 def __eq__(self, other):
342 if not isinstance(other, browse_record):
344 return (self._table_name, self._id) == (other._table_name, other._id)
346 def __ne__(self, other):
347 if not isinstance(other, browse_record):
349 return (self._table_name, self._id) != (other._table_name, other._id)
351 # we need to define __unicode__ even though we've already defined __str__
352 # because we have overridden __getattr__
353 def __unicode__(self):
354 return unicode(str(self))
357 return hash((self._table_name, self._id))
365 (type returned by postgres when the column was created, type expression to create the column)
369 fields.boolean: 'bool',
370 fields.integer: 'int4',
371 fields.integer_big: 'int8',
375 fields.datetime: 'timestamp',
376 fields.binary: 'bytea',
377 fields.many2one: 'int4',
379 if type(f) in type_dict:
380 f_type = (type_dict[type(f)], type_dict[type(f)])
381 elif isinstance(f, fields.float):
383 f_type = ('numeric', 'NUMERIC')
385 f_type = ('float8', 'DOUBLE PRECISION')
386 elif isinstance(f, (fields.char, fields.reference)):
387 f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
388 elif isinstance(f, fields.selection):
389 if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
390 f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
391 elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
394 f_size = getattr(f, 'size', None) or 16
397 f_type = ('int4', 'INTEGER')
399 f_type = ('varchar', 'VARCHAR(%d)' % f_size)
400 elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
401 t = eval('fields.'+(f._type), globals())
402 f_type = (type_dict[t], type_dict[t])
403 elif isinstance(f, fields.function) and f._type == 'float':
405 f_type = ('numeric', 'NUMERIC')
407 f_type = ('float8', 'DOUBLE PRECISION')
408 elif isinstance(f, fields.function) and f._type == 'selection':
409 f_type = ('text', 'text')
410 elif isinstance(f, fields.function) and f._type == 'char':
411 f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
413 logger = netsvc.Logger()
414 logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
419 class orm_template(object):
420 """ Base class for OpenERP models.
422 OpenERP models are created by inheriting from this class (although
423 not directly; more specifically by inheriting from osv or
424 osv_memory). The constructor is called once, usually directly
425 after the class definition, e.g.:
431 The system will later instanciate the class once per database (on
432 which the class' module is installed).
440 _parent_name = 'parent_id'
441 _parent_store = False
442 _parent_order = False
452 CONCURRENCY_CHECK_FIELD = '__last_update'
453 def log(self, cr, uid, id, message, secondary=False, context=None):
454 return self.pool.get('res.log').create(cr, uid,
457 'res_model': self._name,
458 'secondary': secondary,
464 def view_init(self, cr, uid, fields_list, context=None):
465 """Override this method to do specific things when a view on the object is opened."""
468 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
469 raise NotImplementedError(_('The read_group method is not implemented on this object !'))
471 def _field_create(self, cr, context=None):
474 Create/update entries in ir_model, ir_model_data, and ir_model_fields.
476 - create an entry in ir_model (if there is not already one),
477 - create an entry in ir_model_data (if there is not already one, and if
478 'module' is in the context),
479 - update ir_model_fields with the fields found in _columns
480 (TODO there is some redundancy as _columns is updated from
481 ir_model_fields in __init__).
486 cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
488 cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
489 model_id = cr.fetchone()[0]
490 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'))
492 model_id = cr.fetchone()[0]
493 if 'module' in context:
494 name_id = 'model_'+self._name.replace('.', '_')
495 cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
497 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
498 (name_id, context['module'], 'ir.model', model_id)
503 cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
505 for rec in cr.dictfetchall():
506 cols[rec['name']] = rec
508 for (k, f) in self._columns.items():
510 'model_id': model_id,
513 'field_description': f.string.replace("'", " "),
515 'relation': f._obj or '',
516 'view_load': (f.view_load and 1) or 0,
517 'select_level': tools.ustr(f.select or 0),
518 'readonly': (f.readonly and 1) or 0,
519 'required': (f.required and 1) or 0,
520 'selectable': (f.selectable and 1) or 0,
521 'translate': (f.translate and 1) or 0,
522 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
524 # When its a custom field,it does not contain f.select
525 if context.get('field_state', 'base') == 'manual':
526 if context.get('field_name', '') == k:
527 vals['select_level'] = context.get('select', '0')
528 #setting value to let the problem NOT occur next time
530 vals['select_level'] = cols[k]['select_level']
533 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
534 id = cr.fetchone()[0]
536 cr.execute("""INSERT INTO ir_model_fields (
537 id, model_id, model, name, field_description, ttype,
538 relation,view_load,state,select_level,relation_field, translate
540 %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
542 id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
543 vals['relation'], bool(vals['view_load']), 'base',
544 vals['select_level'], vals['relation_field'], bool(vals['translate'])
546 if 'module' in context:
547 name1 = 'field_' + self._table + '_' + k
548 cr.execute("select name from ir_model_data where name=%s", (name1,))
550 name1 = name1 + "_" + str(id)
551 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
552 (name1, context['module'], 'ir.model.fields', id)
555 for key, val in vals.items():
556 if cols[k][key] != vals[key]:
557 cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
559 cr.execute("""UPDATE ir_model_fields SET
560 model_id=%s, field_description=%s, ttype=%s, relation=%s,
561 view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s
563 model=%s AND name=%s""", (
564 vals['model_id'], vals['field_description'], vals['ttype'],
565 vals['relation'], bool(vals['view_load']),
566 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['model'], vals['name']
571 def _auto_init(self, cr, context=None):
572 raise_on_invalid_object_name(self._name)
573 self._field_create(cr, context=context)
576 # Goal: try to apply inheritance at the instanciation level and
577 # put objects in the pool var
580 def makeInstance(cls, pool, cr, attributes):
581 """ Instanciate a given model.
583 This class method instanciates the class of some model (i.e. a class
584 deriving from osv or osv_memory). The class might be the class passed
585 in argument or, if it inherits from another class, a class constructed
586 by combining the two classes.
588 The ``attributes`` argument specifies which parent class attributes
591 TODO: the creation of the combined class is repeated at each call of
592 this method. This is probably unnecessary.
595 parent_names = getattr(cls, '_inherit', None)
597 if isinstance(parent_names, (str, unicode)):
598 name = cls._name or parent_names
599 parent_names = [parent_names]
604 raise TypeError('_name is mandatory in case of multiple inheritance')
606 for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
607 parent_class = pool.get(parent_name).__class__
608 if not pool.get(parent_name):
609 raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
610 'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
613 new = copy.copy(getattr(pool.get(parent_name), s))
615 # Don't _inherit custom fields.
619 if hasattr(new, 'update'):
620 new.update(cls.__dict__.get(s, {}))
621 elif s=='_constraints':
622 for c in cls.__dict__.get(s, []):
624 for c2 in range(len(new)):
625 #For _constraints, we should check field and methods as well
626 if new[c2][2]==c[2] and (new[c2][0] == c[0] \
627 or getattr(new[c2][0],'__name__', True) == \
628 getattr(c[0],'__name__', False)):
629 # If new class defines a constraint with
630 # same function name, we let it override
638 new.extend(cls.__dict__.get(s, []))
640 cls = type(name, (cls, parent_class), nattr)
641 obj = object.__new__(cls)
642 obj.__init__(pool, cr)
646 """ Register this model.
648 This doesn't create an instance but simply register the model
649 as being part of the module where it is defined.
651 TODO make it possible to not even have to call the constructor
656 # Set the module name (e.g. base, sale, accounting, ...) on the class.
657 module = cls.__module__.split('.')[0]
658 if not hasattr(cls, '_module'):
661 # Remember which models to instanciate for this module.
662 module_class_list.setdefault(cls._module, []).append(cls)
664 # Since we don't return an instance here, the __init__
665 # method won't be called.
668 def __init__(self, pool, cr):
669 """ Initialize a model and make it part of the given registry."""
670 pool.add(self._name, self)
673 if not self._name and not hasattr(self, '_inherit'):
674 name = type(self).__name__.split('.')[0]
675 msg = "The class %s has to have a _name attribute" % name
677 logger = netsvc.Logger()
678 logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
679 raise except_orm('ValueError', msg)
681 if not self._description:
682 self._description = self._name
684 self._table = self._name.replace('.', '_')
686 def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
687 """Fetch records as objects allowing to use dot notation to browse fields and relations
689 :param cr: database cursor
690 :param user: current user id
691 :param select: id or list of ids
692 :param context: context arguments, like lang, time zone
693 :rtype: object or list of objects requested
696 self._list_class = list_class or browse_record_list
698 # need to accepts ints and longs because ids coming from a method
699 # launched by button in the interface have a type long...
700 if isinstance(select, (int, long)):
701 return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
702 elif isinstance(select, list):
703 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)
707 def __export_row(self, cr, uid, row, fields, context=None):
711 def check_type(field_type):
712 if field_type == 'float':
714 elif field_type == 'integer':
716 elif field_type == 'boolean':
720 def selection_field(in_field):
721 col_obj = self.pool.get(in_field.keys()[0])
722 if f[i] in col_obj._columns.keys():
723 return col_obj._columns[f[i]]
724 elif f[i] in col_obj._inherits.keys():
725 selection_field(col_obj._inherits)
730 data = map(lambda x: '', range(len(fields)))
732 for fpos in range(len(fields)):
741 model_data = self.pool.get('ir.model.data')
742 data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
744 d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
746 r = '%s.%s' % (d['module'], d['name'])
753 # To display external name of selection field when its exported
755 if f[i] in self._columns.keys():
756 cols = self._columns[f[i]]
757 elif f[i] in self._inherit_fields.keys():
758 cols = selection_field(self._inherits)
759 if cols and cols._type == 'selection':
760 sel_list = cols.selection
761 if r and type(sel_list) == type([]):
762 r = [x[1] for x in sel_list if r==x[0]]
763 r = r and r[0] or False
765 if f[i] in self._columns:
766 r = check_type(self._columns[f[i]]._type)
767 elif f[i] in self._inherit_fields:
768 r = check_type(self._inherit_fields[f[i]][2]._type)
769 data[fpos] = r or False
771 if isinstance(r, (browse_record_list, list)):
773 fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
776 if [x for x in fields2 if x]:
780 lines2 = self.__export_row(cr, uid, row2, fields2,
783 for fpos2 in range(len(fields)):
784 if lines2 and lines2[0][fpos2]:
785 data[fpos2] = lines2[0][fpos2]
789 name_relation = self.pool.get(rr._table_name)._rec_name
790 if isinstance(rr[name_relation], browse_record):
791 rr = rr[name_relation]
792 rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
793 rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
794 dt += tools.ustr(rr_name or '') + ','
804 if isinstance(r, browse_record):
805 r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
806 r = r and r[0] and r[0][1] or ''
807 data[fpos] = tools.ustr(r or '')
808 return [data] + lines
810 def export_data(self, cr, uid, ids, fields_to_export, context=None):
812 Export fields for selected objects
814 :param cr: database cursor
815 :param uid: current user id
816 :param ids: list of ids
817 :param fields_to_export: list of fields
818 :param context: context arguments, like lang, time zone
819 :rtype: dictionary with a *datas* matrix
821 This method is used when exporting data via client menu
826 cols = self._columns.copy()
827 for f in self._inherit_fields:
828 cols.update({f: self._inherit_fields[f][2]})
830 if x=='.id': return [x]
831 return x.replace(':id','/id').replace('.id','/.id').split('/')
832 fields_to_export = map(fsplit, fields_to_export)
834 for row in self.browse(cr, uid, ids, context):
835 datas += self.__export_row(cr, uid, row, fields_to_export, context)
836 return {'datas': datas}
838 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
840 Import given data in given module
842 :param cr: database cursor
843 :param uid: current user id
844 :param fields: list of fields
845 :param data: data to import
846 :param mode: 'init' or 'update' for record creation
847 :param current_module: module name
848 :param noupdate: flag for record creation
849 :param context: context arguments, like lang, time zone,
850 :param filename: optional file to store partial import state for recovery
853 This method is used when importing data via client menu.
855 Example of fields to import for a sale.order::
858 partner_id, (=name_search)
859 order_line/.id, (=database_id)
861 order_line/product_id/id, (=xml id)
862 order_line/price_unit,
863 order_line/product_uom_qty,
864 order_line/product_uom/id (=xml_id)
868 def _replace_field(x):
869 x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
870 return x.replace(':id','/id').split('/')
871 fields = map(_replace_field, fields)
872 logger = netsvc.Logger()
873 ir_model_data_obj = self.pool.get('ir.model.data')
875 # mode: id (XML id) or .id (database id) or False for name_get
876 def _get_id(model_name, id, current_module=False, mode='id'):
879 obj_model = self.pool.get(model_name)
880 ids = obj_model.search(cr, uid, [('id', '=', int(id))])
882 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
885 module, xml_id = id.rsplit('.', 1)
887 module, xml_id = current_module, id
888 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
889 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
890 if not ir_model_data:
891 raise ValueError('No references to %s.%s' % (module, xml_id))
892 id = ir_model_data[0]['res_id']
894 obj_model = self.pool.get(model_name)
895 ids = obj_model.name_search(cr, uid, id, operator='=', context=context)
897 raise ValueError('No record found for %s' % (id,))
902 # datas: a list of records, each record is defined by a list of values
903 # prefix: a list of prefix fields ['line_ids']
904 # position: the line to process, skip is False if it's the first line of the current record
906 # (res, position, warning, res_id) with
907 # res: the record for the next line to process (including it's one2many)
908 # position: the new position for the next line
909 # res_id: the ID of the record if it's a modification
910 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
911 line = datas[position]
919 for i in range(len(fields)):
922 raise Exception(_('Please check that all your lines have %d columns.'
923 'Stopped around line %d having %d columns.') % \
924 (len(fields), position+2, len(line)))
929 if field[:len(prefix)] <> prefix:
934 # ID of the record using a XML ID
935 if field[len(prefix)]=='id':
937 data_res_id = _get_id(model_name, line[i], current_module, 'id')
938 except ValueError, e:
943 # ID of the record using a database ID
944 elif field[len(prefix)]=='.id':
945 data_res_id = _get_id(model_name, line[i], current_module, '.id')
948 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
949 if fields_def[field[len(prefix)]]['type']=='one2many':
950 if field[len(prefix)] in done:
952 done[field[len(prefix)]] = True
953 relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
954 newfd = relation_obj.fields_get( cr, uid, context=context )
958 while pos < len(datas):
959 res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
962 (newrow, pos, w2, data_res_id2, xml_id2) = res2
963 nbrmax = max(nbrmax, pos)
966 if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
968 res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
970 elif fields_def[field[len(prefix)]]['type']=='many2one':
971 relation = fields_def[field[len(prefix)]]['relation']
972 if len(field) == len(prefix)+1:
975 mode = field[len(prefix)+1]
976 res = _get_id(relation, line[i], current_module, mode)
978 elif fields_def[field[len(prefix)]]['type']=='many2many':
979 relation = fields_def[field[len(prefix)]]['relation']
980 if len(field) == len(prefix)+1:
983 mode = field[len(prefix)+1]
985 # TODO: improve this by using csv.csv_reader
987 for db_id in line[i].split(config.get('csv_internal_sep')):
988 res.append( _get_id(relation, db_id, current_module, mode) )
991 elif fields_def[field[len(prefix)]]['type'] == 'integer':
992 res = line[i] and int(line[i]) or 0
993 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
994 res = line[i].lower() not in ('0', 'false', 'off')
995 elif fields_def[field[len(prefix)]]['type'] == 'float':
996 res = line[i] and float(line[i]) or 0.0
997 elif fields_def[field[len(prefix)]]['type'] == 'selection':
998 for key, val in fields_def[field[len(prefix)]]['selection']:
999 if tools.ustr(line[i]) in [tools.ustr(key), tools.ustr(val)]:
1002 if line[i] and not res:
1003 logger.notifyChannel("import", netsvc.LOG_WARNING,
1004 _("key '%s' not found in selection field '%s'") % \
1005 (tools.ustr(line[i]), tools.ustr(field[len(prefix)])))
1006 warning += [_("Key/value '%s' not found in selection field '%s'") % (tools.ustr(line[i]), tools.ustr(field[len(prefix)]))]
1011 row[field[len(prefix)]] = res or False
1013 result = (row, nbrmax, warning, data_res_id, xml_id)
1016 fields_def = self.fields_get(cr, uid, context=context)
1018 if config.get('import_partial', False) and filename:
1019 data = pickle.load(file(config.get('import_partial')))
1022 while position<len(datas):
1025 (res, position, warning, res_id, xml_id) = \
1026 process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
1029 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
1032 ir_model_data_obj._update(cr, uid, self._name,
1033 current_module, res, mode=mode, xml_id=xml_id,
1034 noupdate=noupdate, res_id=res_id, context=context)
1035 except Exception, e:
1036 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
1038 if config.get('import_partial', False) and filename and (not (position%100)):
1039 data = pickle.load(file(config.get('import_partial')))
1040 data[filename] = position
1041 pickle.dump(data, file(config.get('import_partial'), 'wb'))
1042 if context.get('defer_parent_store_computation'):
1043 self._parent_store_compute(cr)
1046 if context.get('defer_parent_store_computation'):
1047 self._parent_store_compute(cr)
1048 return (position, 0, 0, 0)
1050 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1052 Read records with given ids with the given fields
1054 :param cr: database cursor
1055 :param user: current user id
1056 :param ids: id or list of the ids of the records to read
1057 :param fields: optional list of field names to return (default: all fields would be returned)
1058 :type fields: list (example ['field_name_1', ...])
1059 :param context: optional context dictionary - it may contains keys for specifying certain options
1060 like ``context_lang``, ``context_tz`` to alter the results of the call.
1061 A special ``bin_size`` boolean flag may also be passed in the context to request the
1062 value of all fields.binary columns to be returned as the size of the binary instead of its
1063 contents. This can also be selectively overriden by passing a field-specific flag
1064 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1065 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1066 :return: list of dictionaries((dictionary per record asked)) with requested field values
1067 :rtype: [{‘name_of_the_field’: value, ...}, ...]
1068 :raise AccessError: * if user has no read rights on the requested object
1069 * if user tries to bypass access rules for read on the requested object
1072 raise NotImplementedError(_('The read method is not implemented on this object !'))
1074 def get_invalid_fields(self, cr, uid):
1075 return list(self._invalids)
1077 def _validate(self, cr, uid, ids, context=None):
1078 context = context or {}
1079 lng = context.get('lang', False) or 'en_US'
1080 trans = self.pool.get('ir.translation')
1082 for constraint in self._constraints:
1083 fun, msg, fields = constraint
1084 if not fun(self, cr, uid, ids):
1085 # Check presence of __call__ directly instead of using
1086 # callable() because it will be deprecated as of Python 3.0
1087 if hasattr(msg, '__call__'):
1088 tmp_msg = msg(self, cr, uid, ids, context=context)
1089 if isinstance(tmp_msg, tuple):
1090 tmp_msg, params = tmp_msg
1091 translated_msg = tmp_msg % params
1093 translated_msg = tmp_msg
1095 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1097 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1099 self._invalids.update(fields)
1102 raise except_orm('ValidateError', '\n'.join(error_msgs))
1104 self._invalids.clear()
1106 def default_get(self, cr, uid, fields_list, context=None):
1108 Returns default values for the fields in fields_list.
1110 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1111 :type fields_list: list
1112 :param context: optional context dictionary - it may contains keys for specifying certain options
1113 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1114 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1115 or override a default value for a field.
1116 A special ``bin_size`` boolean flag may also be passed in the context to request the
1117 value of all fields.binary columns to be returned as the size of the binary instead of its
1118 contents. This can also be selectively overriden by passing a field-specific flag
1119 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1120 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1121 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1123 # trigger view init hook
1124 self.view_init(cr, uid, fields_list, context)
1130 # get the default values for the inherited fields
1131 for t in self._inherits.keys():
1132 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1135 # get the default values defined in the object
1136 for f in fields_list:
1137 if f in self._defaults:
1138 if callable(self._defaults[f]):
1139 defaults[f] = self._defaults[f](self, cr, uid, context)
1141 defaults[f] = self._defaults[f]
1143 fld_def = ((f in self._columns) and self._columns[f]) \
1144 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1147 if isinstance(fld_def, fields.property):
1148 property_obj = self.pool.get('ir.property')
1149 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1151 if isinstance(prop_value, (browse_record, browse_null)):
1152 defaults[f] = prop_value.id
1154 defaults[f] = prop_value
1156 if f not in defaults:
1159 # get the default values set by the user and override the default
1160 # values defined in the object
1161 ir_values_obj = self.pool.get('ir.values')
1162 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1163 for id, field, field_value in res:
1164 if field in fields_list:
1165 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1166 if fld_def._type in ('many2one', 'one2one'):
1167 obj = self.pool.get(fld_def._obj)
1168 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1170 if fld_def._type in ('many2many'):
1171 obj = self.pool.get(fld_def._obj)
1173 for i in range(len(field_value)):
1174 if not obj.search(cr, uid, [('id', '=',
1177 field_value2.append(field_value[i])
1178 field_value = field_value2
1179 if fld_def._type in ('one2many'):
1180 obj = self.pool.get(fld_def._obj)
1182 for i in range(len(field_value)):
1183 field_value2.append({})
1184 for field2 in field_value[i]:
1185 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1186 obj2 = self.pool.get(obj._columns[field2]._obj)
1187 if not obj2.search(cr, uid,
1188 [('id', '=', field_value[i][field2])]):
1190 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1191 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1192 if not obj2.search(cr, uid,
1193 [('id', '=', field_value[i][field2])]):
1195 # TODO add test for many2many and one2many
1196 field_value2[i][field2] = field_value[i][field2]
1197 field_value = field_value2
1198 defaults[field] = field_value
1200 # get the default values from the context
1201 for key in context or {}:
1202 if key.startswith('default_') and (key[8:] in fields_list):
1203 defaults[key[8:]] = context[key]
1207 def perm_read(self, cr, user, ids, context=None, details=True):
1208 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1210 def unlink(self, cr, uid, ids, context=None):
1211 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1213 def write(self, cr, user, ids, vals, context=None):
1214 raise NotImplementedError(_('The write method is not implemented on this object !'))
1216 def create(self, cr, user, vals, context=None):
1217 raise NotImplementedError(_('The create method is not implemented on this object !'))
1219 def fields_get_keys(self, cr, user, context=None):
1220 res = self._columns.keys()
1221 for parent in self._inherits:
1222 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1225 # returns the definition of each field in the object
1226 # the optional fields parameter can limit the result to some fields
1227 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1231 translation_obj = self.pool.get('ir.translation')
1232 for parent in self._inherits:
1233 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1235 if self._columns.keys():
1236 for f in self._columns.keys():
1237 field_col = self._columns[f]
1238 if allfields and f not in allfields:
1240 res[f] = {'type': field_col._type}
1241 # This additional attributes for M2M and function field is added
1242 # because we need to display tooltip with this additional information
1243 # when client is started in debug mode.
1244 if isinstance(field_col, fields.function):
1245 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1246 res[f]['store'] = field_col.store
1247 if isinstance(field_col.store, dict):
1248 res[f]['store'] = str(field_col.store)
1249 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1250 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1251 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1252 res[f]['func_obj'] = field_col._obj or False
1253 res[f]['func_method'] = field_col._method
1254 if isinstance(field_col, fields.many2many):
1255 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1256 res[f]['third_table'] = field_col._rel
1257 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1258 'change_default', 'translate', 'help', 'select', 'selectable'):
1259 if getattr(field_col, arg):
1260 res[f][arg] = getattr(field_col, arg)
1261 if not write_access:
1262 res[f]['readonly'] = True
1263 res[f]['states'] = {}
1264 for arg in ('digits', 'invisible', 'filters'):
1265 if getattr(field_col, arg, None):
1266 res[f][arg] = getattr(field_col, arg)
1268 if field_col.string:
1269 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1271 res[f]['string'] = res_trans
1273 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1275 res[f]['help'] = help_trans
1277 if hasattr(field_col, 'selection'):
1278 if isinstance(field_col.selection, (tuple, list)):
1279 sel = field_col.selection
1280 # translate each selection option
1282 for (key, val) in sel:
1285 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1286 sel2.append((key, val2 or val))
1288 res[f]['selection'] = sel
1290 # call the 'dynamic selection' function
1291 res[f]['selection'] = field_col.selection(self, cr, user, context)
1292 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1293 res[f]['relation'] = field_col._obj
1294 res[f]['domain'] = field_col._domain
1295 res[f]['context'] = field_col._context
1297 #TODO : read the fields from the database
1301 # filter out fields which aren't in the fields list
1302 for r in res.keys():
1303 if r not in allfields:
1308 # Overload this method if you need a window title which depends on the context
1310 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1313 def __view_look_dom(self, cr, user, node, view_id, context=None):
1321 if isinstance(s, unicode):
1322 return s.encode('utf8')
1325 # return True if node can be displayed to current user
1326 def check_group(node):
1327 if node.get('groups'):
1328 groups = node.get('groups').split(',')
1329 access_pool = self.pool.get('ir.model.access')
1330 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1332 node.set('invisible', '1')
1333 if 'attrs' in node.attrib:
1334 del(node.attrib['attrs']) #avoid making field visible later
1335 del(node.attrib['groups'])
1340 if node.tag in ('field', 'node', 'arrow'):
1341 if node.get('object'):
1346 if f.tag in ('field'):
1347 xml += etree.tostring(f, encoding="utf-8")
1349 new_xml = etree.fromstring(encode(xml))
1350 ctx = context.copy()
1351 ctx['base_model_name'] = self._name
1352 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1357 attrs = {'views': views}
1359 if node.get('name'):
1362 if node.get('name') in self._columns:
1363 column = self._columns[node.get('name')]
1365 column = self._inherit_fields[node.get('name')][2]
1370 relation = self.pool.get(column._obj)
1375 if f.tag in ('form', 'tree', 'graph'):
1377 ctx = context.copy()
1378 ctx['base_model_name'] = self._name
1379 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1380 views[str(f.tag)] = {
1384 attrs = {'views': views}
1385 if node.get('widget') and node.get('widget') == 'selection':
1386 # Prepare the cached selection list for the client. This needs to be
1387 # done even when the field is invisible to the current user, because
1388 # other events could need to change its value to any of the selectable ones
1389 # (such as on_change events, refreshes, etc.)
1391 # If domain and context are strings, we keep them for client-side, otherwise
1392 # we evaluate them server-side to consider them when generating the list of
1394 # TODO: find a way to remove this hack, by allow dynamic domains
1396 if column._domain and not isinstance(column._domain, basestring):
1397 dom = column._domain
1398 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1399 search_context = dict(context)
1400 if column._context and not isinstance(column._context, basestring):
1401 search_context.update(column._context)
1402 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1403 if (node.get('required') and not int(node.get('required'))) or not column.required:
1404 attrs['selection'].append((False, ''))
1405 fields[node.get('name')] = attrs
1407 elif node.tag in ('form', 'tree'):
1408 result = self.view_header_get(cr, user, False, node.tag, context)
1410 node.set('string', result)
1412 elif node.tag == 'calendar':
1413 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1414 if node.get(additional_field):
1415 fields[node.get(additional_field)] = {}
1417 if 'groups' in node.attrib:
1421 if ('lang' in context) and not result:
1422 if node.get('string'):
1423 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1424 if trans == node.get('string') and ('base_model_name' in context):
1425 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1426 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1427 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1429 node.set('string', trans)
1430 if node.get('confirm'):
1431 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1433 node.set('confirm', trans)
1435 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1437 node.set('sum', trans)
1438 if node.get('help'):
1439 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('help'))
1441 node.set('help', trans)
1444 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1445 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1449 def _disable_workflow_buttons(self, cr, user, node):
1451 # admin user can always activate workflow buttons
1454 # TODO handle the case of more than one workflow for a model or multiple
1455 # transitions with different groups and same signal
1456 usersobj = self.pool.get('res.users')
1457 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1458 for button in buttons:
1459 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1460 cr.execute("""SELECT DISTINCT t.group_id
1462 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1463 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1466 AND t.group_id is NOT NULL
1467 """, (self._name, button.get('name')))
1468 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1469 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1470 button.set('readonly', str(int(not can_click)))
1473 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1474 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1475 node = self._disable_workflow_buttons(cr, user, node)
1476 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1478 if node.tag == 'diagram':
1479 if node.getchildren()[0].tag == 'node':
1480 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1481 if node.getchildren()[1].tag == 'arrow':
1482 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1483 for key, value in node_fields.items():
1485 for key, value in arrow_fields.items():
1488 fields = self.fields_get(cr, user, fields_def.keys(), context)
1489 for field in fields_def:
1491 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1492 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1493 elif field in fields:
1494 fields[field].update(fields_def[field])
1496 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))
1497 res = cr.fetchall()[:]
1499 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1500 msg = "\n * ".join([r[0] for r in res])
1501 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1502 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1503 raise except_orm('View error', msg)
1506 def __get_default_calendar_view(self):
1507 """Generate a default calendar view (For internal use only).
1510 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1511 '<calendar string="%s"') % (self._description)
1513 if (self._date_name not in self._columns):
1515 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1516 if dt in self._columns:
1517 self._date_name = dt
1522 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1525 arch += ' date_start="%s"' % (self._date_name)
1527 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1528 if color in self._columns:
1529 arch += ' color="' + color + '"'
1532 dt_stop_flag = False
1534 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1535 if dt_stop in self._columns:
1536 arch += ' date_stop="' + dt_stop + '"'
1540 if not dt_stop_flag:
1541 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1542 if dt_delay in self._columns:
1543 arch += ' date_delay="' + dt_delay + '"'
1547 ' <field name="%s"/>\n'
1548 '</calendar>') % (self._rec_name)
1552 def __get_default_search_view(self, cr, uid, context=None):
1553 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1554 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1556 fields_to_search = set()
1557 fields = self.fields_get(cr, uid, context=context)
1558 for field in fields:
1559 if fields[field].get('select'):
1560 fields_to_search.add(field)
1561 for view in (form_view, tree_view):
1562 view_root = etree.fromstring(view['arch'])
1563 # Only care about select=1 in xpath below, because select=2 is covered
1564 # by the custom advanced search in clients
1565 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1567 tree_view_root = view_root # as provided by loop above
1568 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1569 field_group = etree.Element("group")
1570 search_view.append(field_group)
1572 for field_name in fields_to_search:
1573 field_group.append(etree.Element("field", attrib={'name': field_name}))
1575 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1578 # if view_id, view_type is not required
1580 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1582 Get the detailed composition of the requested view like fields, model, view architecture
1584 :param cr: database cursor
1585 :param user: current user id
1586 :param view_id: id of the view or None
1587 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1588 :param context: context arguments, like lang, time zone
1589 :param toolbar: true to include contextual actions
1590 :param submenu: example (portal_project module)
1591 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1592 :raise AttributeError:
1593 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1594 * if some tag other than 'position' is found in parent view
1595 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1602 if isinstance(s, unicode):
1603 return s.encode('utf8')
1606 def raise_view_error(error_msg, child_view_id):
1607 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1608 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1609 % { 'xml_id': child_view.xml_id,
1610 'parent_xml_id': view.xml_id,
1611 'model': self._name, })
1613 def _inherit_apply(src, inherit, inherit_id=None):
1614 def _find(node, node2):
1615 if node2.tag == 'xpath':
1616 res = node.xpath(node2.get('expr'))
1622 for n in node.getiterator(node2.tag):
1624 if node2.tag == 'field':
1625 # only compare field names, a field can be only once in a given view
1626 # at a given level (and for multilevel expressions, we should use xpath
1627 # inheritance spec anyway)
1628 if node2.get('name') == n.get('name'):
1632 for attr in node2.attrib:
1633 if attr == 'position':
1636 if n.get(attr) == node2.get(attr):
1643 # End: _find(node, node2)
1645 doc_dest = etree.fromstring(encode(inherit))
1646 toparse = [doc_dest]
1649 node2 = toparse.pop(0)
1650 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1652 if node2.tag == 'data':
1653 toparse += [ c for c in doc_dest ]
1655 node = _find(src, node2)
1656 if node is not None:
1658 if node2.get('position'):
1659 pos = node2.get('position')
1660 if pos == 'replace':
1661 parent = node.getparent()
1663 src = copy.deepcopy(node2[0])
1666 node.addprevious(child)
1667 node.getparent().remove(node)
1668 elif pos == 'attributes':
1669 for child in node2.getiterator('attribute'):
1670 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1672 node.set(attribute[0], attribute[1])
1674 del(node.attrib[attribute[0]])
1676 sib = node.getnext()
1680 elif pos == 'after':
1685 sib.addprevious(child)
1686 elif pos == 'before':
1687 node.addprevious(child)
1689 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1692 ' %s="%s"' % (attr, node2.get(attr))
1693 for attr in node2.attrib
1694 if attr != 'position'
1696 tag = "<%s%s>" % (node2.tag, attrs)
1697 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1699 # End: _inherit_apply(src, inherit)
1701 result = {'type': view_type, 'model': self._name}
1705 parent_view_model = None
1707 view_ref = context.get(view_type + '_view_ref', False)
1708 if view_ref and not view_id:
1710 module, view_ref = view_ref.split('.', 1)
1711 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1712 view_ref_res = cr.fetchone()
1714 view_id = view_ref_res[0]
1717 cr.execute("""SELECT arch,name,field_parent,id,type,inherit_id,model
1719 WHERE id=%s""", (view_id,))
1721 cr.execute('''SELECT
1722 arch,name,field_parent,id,type,inherit_id,model
1729 ORDER BY priority''', (self._name, view_type))
1730 sql_res = cr.fetchone()
1736 view_id = ok or sql_res[3]
1737 parent_view_model = sql_res[6]
1739 # if a view was found
1741 result['type'] = sql_res[4]
1742 result['view_id'] = sql_res[3]
1743 result['arch'] = sql_res[0]
1745 def _inherit_apply_rec(result, inherit_id):
1746 # get all views which inherit from (ie modify) this view
1747 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1748 sql_inherit = cr.fetchall()
1749 for (inherit, id) in sql_inherit:
1750 result = _inherit_apply(result, inherit, id)
1751 result = _inherit_apply_rec(result, id)
1754 inherit_result = etree.fromstring(encode(result['arch']))
1755 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1757 result['name'] = sql_res[1]
1758 result['field_parent'] = sql_res[2] or False
1761 # otherwise, build some kind of default view
1762 if view_type == 'form':
1763 res = self.fields_get(cr, user, context=context)
1764 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1765 '<form string="%s">' % (self._description,)
1767 if res[x]['type'] not in ('one2many', 'many2many'):
1768 xml += '<field name="%s"/>' % (x,)
1769 if res[x]['type'] == 'text':
1773 elif view_type == 'tree':
1774 _rec_name = self._rec_name
1775 if _rec_name not in self._columns:
1776 _rec_name = self._columns.keys()[0]
1777 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1778 '<tree string="%s"><field name="%s"/></tree>' \
1779 % (self._description, _rec_name)
1781 elif view_type == 'calendar':
1782 xml = self.__get_default_calendar_view()
1784 elif view_type == 'search':
1785 xml = self.__get_default_search_view(cr, user, context)
1788 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1789 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1790 result['arch'] = etree.fromstring(encode(xml))
1791 result['name'] = 'default'
1792 result['field_parent'] = False
1793 result['view_id'] = 0
1795 if parent_view_model != self._name:
1796 ctx = context.copy()
1797 ctx['base_model_name'] = parent_view_model
1800 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1801 result['arch'] = xarch
1802 result['fields'] = xfields
1805 if context and context.get('active_id', False):
1806 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1808 act_id = data_menu.id
1810 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1811 result['submenu'] = getattr(data_action, 'menus', False)
1815 for key in ('report_sxw_content', 'report_rml_content',
1816 'report_sxw', 'report_rml',
1817 'report_sxw_content_data', 'report_rml_content_data'):
1821 ir_values_obj = self.pool.get('ir.values')
1822 resprint = ir_values_obj.get(cr, user, 'action',
1823 'client_print_multi', [(self._name, False)], False,
1825 resaction = ir_values_obj.get(cr, user, 'action',
1826 'client_action_multi', [(self._name, False)], False,
1829 resrelate = ir_values_obj.get(cr, user, 'action',
1830 'client_action_relate', [(self._name, False)], False,
1832 resprint = map(clean, resprint)
1833 resaction = map(clean, resaction)
1834 resaction = filter(lambda x: not x.get('multi', False), resaction)
1835 resprint = filter(lambda x: not x.get('multi', False), resprint)
1836 resrelate = map(lambda x: x[2], resrelate)
1838 for x in resprint + resaction + resrelate:
1839 x['string'] = x['name']
1841 result['toolbar'] = {
1843 'action': resaction,
1848 _view_look_dom_arch = __view_look_dom_arch
1850 def search_count(self, cr, user, args, context=None):
1853 res = self.search(cr, user, args, context=context, count=True)
1854 if isinstance(res, list):
1858 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1860 Search for records based on a search domain.
1862 :param cr: database cursor
1863 :param user: current user id
1864 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1865 :param offset: optional number of results to skip in the returned values (default: 0)
1866 :param limit: optional max number of records to return (default: **None**)
1867 :param order: optional columns to sort by (default: self._order=id )
1868 :param context: optional context arguments, like lang, time zone
1869 :type context: dictionary
1870 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1871 :return: id or list of ids of records matching the criteria
1872 :rtype: integer or list of integers
1873 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1875 **Expressing a search domain (args)**
1877 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1879 * **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.
1880 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1881 The semantics of most of these operators are obvious.
1882 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1883 according to the semantics of this model (i.e following the relationship field named by
1884 ``self._parent_name``, by default ``parent_id``.
1885 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1887 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1888 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1889 Be very careful about this when you combine them the first time.
1891 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1893 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1895 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::
1897 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1900 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1902 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1904 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1905 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1906 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1908 :param access_rights_uid: optional user ID to use when checking access rights
1909 (not for ir.rules, this is only for ir.model.access)
1911 raise NotImplementedError(_('The search method is not implemented on this object !'))
1913 def name_get(self, cr, user, ids, context=None):
1916 :param cr: database cursor
1917 :param user: current user id
1919 :param ids: list of ids
1920 :param context: context arguments, like lang, time zone
1921 :type context: dictionary
1922 :return: tuples with the text representation of requested objects for to-many relationships
1929 if isinstance(ids, (int, long)):
1931 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1932 [self._rec_name], context, load='_classic_write')]
1934 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1936 Search for records and their display names according to a search domain.
1938 :param cr: database cursor
1939 :param user: current user id
1940 :param name: object name to search
1941 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1942 :param operator: operator for search criterion
1943 :param context: context arguments, like lang, time zone
1944 :type context: dictionary
1945 :param limit: optional max number of records to return
1946 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1948 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1949 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1952 return self._name_search(cr, user, name, args, operator, context, limit)
1954 def name_create(self, cr, uid, name, context=None):
1956 Creates a new record by calling :py:meth:`~osv.osv.osv.create` with only one
1957 value provided: the name of the new record (``_rec_name`` field).
1958 The new record will also be initialized with any default values applicable
1959 to this model, or provided through the context. The usual behavior of
1960 :py:meth:`~osv.osv.osv.create` applies.
1961 Similarly, this method may raise an exception if the model has multiple
1962 required fields and some do not have default values.
1964 :param name: name of the record to create
1966 :return: the :py:meth:`~osv.osv.osv.name_get` value for the newly-created record.
1968 rec_id = self.create(cr, uid, {self._rec_name: name}, context);
1969 return self.name_get(cr, uid, [rec_id], context)[0]
1971 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1972 # solve some access rights issues
1973 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1980 args += [(self._rec_name, operator, name)]
1981 access_rights_uid = name_get_uid or user
1982 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1983 res = self.name_get(cr, access_rights_uid, ids, context)
1986 def copy(self, cr, uid, id, default=None, context=None):
1987 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1989 def exists(self, cr, uid, id, context=None):
1990 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1992 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1995 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1997 fields = self._columns.keys() + self._inherit_fields.keys()
1998 #FIXME: collect all calls to _get_source into one SQL call.
2000 res[lang] = {'code': lang}
2002 if f in self._columns:
2003 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
2005 res[lang][f] = res_trans
2007 res[lang][f] = self._columns[f].string
2008 for table in self._inherits:
2009 cols = intersect(self._inherit_fields.keys(), fields)
2010 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
2013 res[lang]['code'] = lang
2014 for f in res2[lang]:
2015 res[lang][f] = res2[lang][f]
2018 def write_string(self, cr, uid, id, langs, vals, context=None):
2019 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
2020 #FIXME: try to only call the translation in one SQL
2023 if field in self._columns:
2024 src = self._columns[field].string
2025 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
2026 for table in self._inherits:
2027 cols = intersect(self._inherit_fields.keys(), vals)
2029 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2032 def _check_removed_columns(self, cr, log=False):
2033 raise NotImplementedError()
2035 def _add_missing_default_values(self, cr, uid, values, context=None):
2036 missing_defaults = []
2037 avoid_tables = [] # avoid overriding inherited values when parent is set
2038 for tables, parent_field in self._inherits.items():
2039 if parent_field in values:
2040 avoid_tables.append(tables)
2041 for field in self._columns.keys():
2042 if not field in values:
2043 missing_defaults.append(field)
2044 for field in self._inherit_fields.keys():
2045 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
2046 missing_defaults.append(field)
2048 if len(missing_defaults):
2049 # override defaults with the provided values, never allow the other way around
2050 defaults = self.default_get(cr, uid, missing_defaults, context)
2052 if ((dv in self._columns and self._columns[dv]._type == 'many2many') \
2053 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many')) \
2054 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
2055 defaults[dv] = [(6, 0, defaults[dv])]
2056 if (dv in self._columns and self._columns[dv]._type == 'one2many' \
2057 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many')) \
2058 and isinstance(defaults[dv], (list, tuple)) and defaults[dv] and isinstance(defaults[dv][0], dict):
2059 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
2060 defaults.update(values)
2064 class orm_memory(orm_template):
2066 _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']
2067 _inherit_fields = {}
2068 _max_count = config.get('osv_memory_count_limit')
2069 _max_hours = config.get('osv_memory_age_limit')
2073 def createInstance(cls, pool, cr):
2074 return cls.makeInstance(pool, cr, ['_columns', '_defaults'])
2076 def __init__(self, pool, cr):
2077 super(orm_memory, self).__init__(pool, cr)
2081 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2083 def _check_access(self, uid, object_id, mode):
2084 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2085 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2087 def vaccum(self, cr, uid, force=False):
2088 """Run the vaccuum cleaning system, expiring and removing old records from the
2089 virtual osv_memory tables if the "max count" or "max age" conditions are enabled
2090 and have been reached. This method can be called very often (e.g. everytime a record
2091 is created), but will only actually trigger the cleanup process once out of
2092 "_check_time" times (by default once out of 20 calls)."""
2094 if (not force) and (self.check_id % self._check_time):
2098 # Age-based expiration
2100 max = time.time() - self._max_hours * 60 * 60
2101 for k,v in self.datas.iteritems():
2102 if v['internal.date_access'] < max:
2104 self.unlink(cr, 1, tounlink)
2106 # Count-based expiration
2107 if self._max_count and len(self.datas) > self._max_count:
2108 # sort by access time to remove only the first/oldest ones in LRU fashion
2109 records = self.datas.items()
2110 records.sort(key=lambda x:x[1]['internal.date_access'])
2111 self.unlink(cr, 1, [x[0] for x in records[:len(self.datas)-self._max_count]])
2115 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2118 if not fields_to_read:
2119 fields_to_read = self._columns.keys()
2123 if isinstance(ids, (int, long)):
2127 for f in fields_to_read:
2128 record = self.datas.get(id)
2130 self._check_access(user, id, 'read')
2131 r[f] = record.get(f, False)
2132 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2135 if id in self.datas:
2136 self.datas[id]['internal.date_access'] = time.time()
2137 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2138 for f in fields_post:
2139 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2140 for record in result:
2141 record[f] = res2[record['id']]
2142 if isinstance(ids_orig, (int, long)):
2146 def write(self, cr, user, ids, vals, context=None):
2152 if self._columns[field]._classic_write:
2153 vals2[field] = vals[field]
2155 upd_todo.append(field)
2156 for object_id in ids:
2157 self._check_access(user, object_id, mode='write')
2158 self.datas[object_id].update(vals2)
2159 self.datas[object_id]['internal.date_access'] = time.time()
2160 for field in upd_todo:
2161 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2162 self._validate(cr, user, [object_id], context)
2163 wf_service = netsvc.LocalService("workflow")
2164 wf_service.trg_write(user, self._name, object_id, cr)
2167 def create(self, cr, user, vals, context=None):
2168 self.vaccum(cr, user)
2170 id_new = self.next_id
2172 vals = self._add_missing_default_values(cr, user, vals, context)
2177 if self._columns[field]._classic_write:
2178 vals2[field] = vals[field]
2180 upd_todo.append(field)
2181 self.datas[id_new] = vals2
2182 self.datas[id_new]['internal.date_access'] = time.time()
2183 self.datas[id_new]['internal.create_uid'] = user
2185 for field in upd_todo:
2186 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2187 self._validate(cr, user, [id_new], context)
2188 if self._log_create and not (context and context.get('no_store_function', False)):
2189 message = self._description + \
2191 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2193 self.log(cr, user, id_new, message, True, context=context)
2194 wf_service = netsvc.LocalService("workflow")
2195 wf_service.trg_create(user, self._name, id_new, cr)
2198 def _where_calc(self, cr, user, args, active_test=True, context=None):
2203 # if the object has a field named 'active', filter out all inactive
2204 # records unless they were explicitely asked for
2205 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2207 active_in_args = False
2209 if a[0] == 'active':
2210 active_in_args = True
2211 if not active_in_args:
2212 args.insert(0, ('active', '=', 1))
2214 args = [('active', '=', 1)]
2217 e = expression.expression(args)
2218 e.parse(cr, user, self, context)
2222 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2226 # implicit filter on current user except for superuser
2230 args.insert(0, ('internal.create_uid', '=', user))
2232 result = self._where_calc(cr, user, args, context=context)
2234 return self.datas.keys()
2238 #Find the value of dict
2241 for id, data in self.datas.items():
2242 counter = counter + 1
2244 if limit and (counter > int(limit)):
2249 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2250 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2251 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2252 elif arg[1] in ['ilike']:
2253 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2263 def unlink(self, cr, uid, ids, context=None):
2265 self._check_access(uid, id, 'unlink')
2266 self.datas.pop(id, None)
2268 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2271 def perm_read(self, cr, user, ids, context=None, details=True):
2273 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2274 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2276 self._check_access(user, id, 'read')
2278 'create_uid': credentials,
2279 'create_date': create_date,
2281 'write_date': False,
2287 def _check_removed_columns(self, cr, log=False):
2288 # nothing to check in memory...
2291 def exists(self, cr, uid, id, context=None):
2292 return id in self.datas
2294 class orm(orm_template):
2295 _sql_constraints = []
2297 _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']
2298 __logger = logging.getLogger('orm')
2299 __schema = logging.getLogger('orm.schema')
2300 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2302 Get the list of records in list view grouped by the given ``groupby`` fields
2304 :param cr: database cursor
2305 :param uid: current user id
2306 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2307 :param list fields: list of fields present in the list view specified on the object
2308 :param list groupby: fields by which the records will be grouped
2309 :param int offset: optional number of records to skip
2310 :param int limit: optional max number of records to return
2311 :param dict context: context arguments, like lang, time zone
2312 :param order: optional ``order by`` specification, for overriding the natural
2313 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2314 (supported only for many2one fields currently)
2315 :return: list of dictionaries(one dictionary for each record) containing:
2317 * the values of fields grouped by the fields in ``groupby`` argument
2318 * __domain: list of tuples specifying the search criteria
2319 * __context: dictionary with argument like ``groupby``
2320 :rtype: [{'field_name_1': value, ...]
2321 :raise AccessError: * if user has no read rights on the requested object
2322 * if user tries to bypass access rules for read on the requested object
2325 context = context or {}
2326 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2328 fields = self._columns.keys()
2330 query = self._where_calc(cr, uid, domain, context=context)
2331 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2333 # Take care of adding join(s) if groupby is an '_inherits'ed field
2334 groupby_list = groupby
2335 qualified_groupby_field = groupby
2337 if isinstance(groupby, list):
2338 groupby = groupby[0]
2339 qualified_groupby_field = self._inherits_join_calc(groupby, query)
2342 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?)"
2343 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2344 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"
2346 fget = self.fields_get(cr, uid, fields)
2347 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2349 group_count = group_by = groupby
2351 if fget.get(groupby):
2352 if fget[groupby]['type'] in ('date', 'datetime'):
2353 flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
2354 groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
2355 qualified_groupby_field = groupby
2357 flist = qualified_groupby_field
2359 # Don't allow arbitrary values, as this would be a SQL injection vector!
2360 raise except_orm(_('Invalid group_by'),
2361 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2364 fields_pre = [f for f in float_int_fields if
2365 f == self.CONCURRENCY_CHECK_FIELD
2366 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2367 for f in fields_pre:
2368 if f not in ['id', 'sequence']:
2369 group_operator = fget[f].get('group_operator', 'sum')
2372 qualified_field = '"%s"."%s"' % (self._table, f)
2373 flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2375 gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2377 from_clause, where_clause, where_clause_params = query.get_sql()
2378 where_clause = where_clause and ' WHERE ' + where_clause
2379 limit_str = limit and ' limit %d' % limit or ''
2380 offset_str = offset and ' offset %d' % offset or ''
2381 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2383 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)
2386 for r in cr.dictfetchall():
2387 for fld, val in r.items():
2388 if val == None: r[fld] = False
2389 alldata[r['id']] = r
2392 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2393 # the IDS of records that have groupby field value = False or '' should be sorted too
2394 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2395 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2396 # 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):
2397 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2401 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2402 if not isinstance(groupby_list, (str, unicode)):
2403 if groupby or not context.get('group_by_no_leaf', False):
2404 d['__context'] = {'group_by': groupby_list[1:]}
2405 if groupby and groupby in fget:
2406 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2407 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2408 days = calendar.monthrange(dt.year, dt.month)[1]
2410 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2411 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),\
2412 (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
2413 del alldata[d['id']][groupby]
2414 d.update(alldata[d['id']])
2418 def _inherits_join_add(self, parent_model_name, query):
2420 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2422 :param parent_model_name: name of the parent model for which the clauses should be added
2423 :param query: query object on which the JOIN should be added
2425 inherits_field = self._inherits[parent_model_name]
2426 parent_model = self.pool.get(parent_model_name)
2427 parent_table_name = parent_model._table
2428 quoted_parent_table_name = '"%s"' % parent_table_name
2429 if quoted_parent_table_name not in query.tables:
2430 query.tables.append(quoted_parent_table_name)
2431 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2433 def _inherits_join_calc(self, field, query):
2435 Adds missing table select and join clause(s) to ``query`` for reaching
2436 the field coming from an '_inherits' parent table (no duplicates).
2438 :param field: name of inherited field to reach
2439 :param query: query object on which the JOIN should be added
2440 :return: qualified name of field, to be used in SELECT clause
2442 current_table = self
2443 while field in current_table._inherit_fields and not field in current_table._columns:
2444 parent_model_name = current_table._inherit_fields[field][0]
2445 parent_table = self.pool.get(parent_model_name)
2446 self._inherits_join_add(parent_model_name, query)
2447 current_table = parent_table
2448 return '"%s".%s' % (current_table._table, field)
2450 def _parent_store_compute(self, cr):
2451 if not self._parent_store:
2453 logger = netsvc.Logger()
2454 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2455 def browse_rec(root, pos=0):
2457 where = self._parent_name+'='+str(root)
2459 where = self._parent_name+' IS NULL'
2460 if self._parent_order:
2461 where += ' order by '+self._parent_order
2462 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2464 for id in cr.fetchall():
2465 pos2 = browse_rec(id[0], pos2)
2466 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2468 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2469 if self._parent_order:
2470 query += ' order by ' + self._parent_order
2473 for (root,) in cr.fetchall():
2474 pos = browse_rec(root, pos)
2477 def _update_store(self, cr, f, k):
2478 logger = netsvc.Logger()
2479 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2480 ss = self._columns[k]._symbol_set
2481 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2482 cr.execute('select id from '+self._table)
2483 ids_lst = map(lambda x: x[0], cr.fetchall())
2486 ids_lst = ids_lst[40:]
2487 res = f.get(cr, self, iids, k, 1, {})
2488 for key, val in res.items():
2491 # if val is a many2one, just write the ID
2492 if type(val) == tuple:
2494 if (val<>False) or (type(val)<>bool):
2495 cr.execute(update_query, (ss[1](val), key))
2497 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2498 """Raise except_orm if value is not among the valid values for the selection field"""
2499 if self._columns[field]._type == 'reference':
2500 val_model, val_id_str = value.split(',', 1)
2503 val_id = long(val_id_str)
2507 raise except_orm(_('ValidateError'),
2508 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2512 if isinstance(self._columns[field].selection, (tuple, list)):
2513 if val in dict(self._columns[field].selection):
2515 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2517 raise except_orm(_('ValidateError'),
2518 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2520 def _check_removed_columns(self, cr, log=False):
2521 # iterate on the database columns to drop the NOT NULL constraints
2522 # of fields which were required but have been removed (or will be added by another module)
2523 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2524 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2525 cr.execute("SELECT a.attname, a.attnotnull"
2526 " FROM pg_class c, pg_attribute a"
2527 " WHERE c.relname=%s"
2528 " AND c.oid=a.attrelid"
2529 " AND a.attisdropped=%s"
2530 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2531 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2533 for column in cr.dictfetchall():
2535 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2536 column['attname'], self._table, self._name)
2537 if column['attnotnull']:
2538 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2539 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2540 self._table, column['attname'])
2542 def _auto_init(self, cr, context=None):
2545 Call _field_create and, unless _auto is False:
2547 - create the corresponding table in database for the model,
2548 - possibly add the parent columns in database,
2549 - possibly add the columns 'create_uid', 'create_date', 'write_uid',
2550 'write_date' in database if _log_access is True (the default),
2551 - report on database columns no more existing in _columns,
2552 - remove no more existing not null constraints,
2553 - alter existing database columns to match _columns,
2554 - create database tables to match _columns,
2555 - add database indices to match _columns,
2558 raise_on_invalid_object_name(self._name)
2561 store_compute = False
2564 self._field_create(cr, context=context)
2565 if getattr(self, '_auto', True):
2566 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2568 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2569 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2571 self.__schema.debug("Table '%s': created", self._table)
2574 if self._parent_store:
2575 cr.execute("""SELECT c.relname
2576 FROM pg_class c, pg_attribute a
2577 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2578 """, (self._table, 'parent_left'))
2580 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2581 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2582 if 'parent_left' not in self._columns:
2583 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2585 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2586 self._table, 'parent_left', 'INTEGER')
2587 elif not self._columns['parent_left'].select:
2588 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2590 if 'parent_right' not in self._columns:
2591 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2593 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2594 self._table, 'parent_right', 'INTEGER')
2595 elif not self._columns['parent_right'].select:
2596 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2598 if self._columns[self._parent_name].ondelete != 'cascade':
2599 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2600 self._parent_name, self._name)
2603 store_compute = True
2605 if self._log_access:
2607 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2608 'create_date': 'TIMESTAMP',
2609 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2610 'write_date': 'TIMESTAMP'
2615 FROM pg_class c, pg_attribute a
2616 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2617 """, (self._table, k))
2619 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2621 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2622 self._table, k, logs[k])
2624 self._check_removed_columns(cr, log=False)
2626 # iterate on the "object columns"
2627 todo_update_store = []
2628 update_custom_fields = context.get('update_custom_fields', False)
2630 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 " \
2631 "FROM pg_class c,pg_attribute a,pg_type t " \
2632 "WHERE c.relname=%s " \
2633 "AND c.oid=a.attrelid " \
2634 "AND a.atttypid=t.oid", (self._table,))
2635 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2638 for k in self._columns:
2639 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2641 #Not Updating Custom fields
2642 if k.startswith('x_') and not update_custom_fields:
2645 f = self._columns[k]
2647 if isinstance(f, fields.one2many):
2648 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2650 if self.pool.get(f._obj):
2651 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2652 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2653 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2656 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))
2657 res = cr.fetchone()[0]
2659 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2660 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2661 self._obj, f._fields_id, f._table)
2662 elif isinstance(f, fields.many2many):
2663 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2664 if not cr.dictfetchall():
2665 if not self.pool.get(f._obj):
2666 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2667 ref = self.pool.get(f._obj)._table
2668 # ref = f._obj.replace('.', '_')
2669 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))
2670 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2671 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2672 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2674 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2675 f._rel, self._table, ref)
2677 res = col_data.get(k, [])
2678 res = res and [res] or []
2679 if not res and hasattr(f, 'oldname'):
2680 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 " \
2681 "FROM pg_class c,pg_attribute a,pg_type t " \
2682 "WHERE c.relname=%s " \
2683 "AND a.attname=%s " \
2684 "AND c.oid=a.attrelid " \
2685 "AND a.atttypid=t.oid", (self._table, f.oldname))
2686 res_old = cr.dictfetchall()
2687 if res_old and len(res_old) == 1:
2688 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2690 res[0]['attname'] = k
2691 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2692 self._table, f.oldname, k)
2696 f_pg_type = f_pg_def['typname']
2697 f_pg_size = f_pg_def['size']
2698 f_pg_notnull = f_pg_def['attnotnull']
2699 if isinstance(f, fields.function) and not f.store and\
2700 not getattr(f, 'nodrop', False):
2701 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2702 k, f.string, self._table)
2703 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2705 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2709 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2714 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2715 ('varchar', 'text', 'TEXT', ''),
2716 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2717 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2718 ('timestamp', 'date', 'date', '::date'),
2719 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2720 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2722 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2723 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2724 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2725 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2726 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2728 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2729 self._table, k, f_pg_size, f.size)
2731 if (f_pg_type==c[0]) and (f._type==c[1]):
2732 if f_pg_type != f_obj_type:
2734 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2735 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2736 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2737 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2739 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2740 self._table, k, c[0], c[1])
2743 if f_pg_type != f_obj_type:
2747 newname = k + '_moved' + str(i)
2748 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2749 "WHERE c.relname=%s " \
2750 "AND a.attname=%s " \
2751 "AND c.oid=a.attrelid ", (self._table, newname))
2752 if not cr.fetchone()[0]:
2756 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2757 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2758 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2759 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2760 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2761 self._table, k, f_pg_type, f._type, newname)
2763 # if the field is required and hasn't got a NOT NULL constraint
2764 if f.required and f_pg_notnull == 0:
2765 # set the field to the default value if any
2766 if k in self._defaults:
2767 if callable(self._defaults[k]):
2768 default = self._defaults[k](self, cr, 1, context)
2770 default = self._defaults[k]
2772 if (default is not None):
2773 ss = self._columns[k]._symbol_set
2774 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2775 cr.execute(query, (ss[1](default),))
2776 # add the NOT NULL constraint
2779 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2781 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2784 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2785 "If you want to have it, you should update the records and execute manually:\n"\
2786 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2787 self.__schema.warn(msg, self._table, k, self._table, k)
2789 elif not f.required and f_pg_notnull == 1:
2790 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2792 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2795 indexname = '%s_%s_index' % (self._table, k)
2796 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2797 res2 = cr.dictfetchall()
2798 if not res2 and f.select:
2799 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2801 if f._type == 'text':
2802 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2803 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2804 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2805 " because there is a length limit for indexable btree values!\n"\
2806 "Use a search view instead if you simply want to make the field searchable."
2807 self.__schema.warn(msg, self._table, k, f._type)
2808 if res2 and not f.select:
2809 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2811 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2812 self.__schema.debug(msg, self._table, k, f._type)
2814 if isinstance(f, fields.many2one):
2815 ref = self.pool.get(f._obj)._table
2816 if ref != 'ir_actions':
2817 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2818 'pg_attribute as att1, pg_attribute as att2 '
2819 'WHERE con.conrelid = cl1.oid '
2820 'AND cl1.relname = %s '
2821 'AND con.confrelid = cl2.oid '
2822 'AND cl2.relname = %s '
2823 'AND array_lower(con.conkey, 1) = 1 '
2824 'AND con.conkey[1] = att1.attnum '
2825 'AND att1.attrelid = cl1.oid '
2826 'AND att1.attname = %s '
2827 'AND array_lower(con.confkey, 1) = 1 '
2828 'AND con.confkey[1] = att2.attnum '
2829 'AND att2.attrelid = cl2.oid '
2830 'AND att2.attname = %s '
2831 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2832 res2 = cr.dictfetchall()
2834 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2835 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2836 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2838 self.__schema.debug("Table '%s': column '%s': XXX",
2841 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2843 if not isinstance(f, fields.function) or f.store:
2844 # add the missing field
2845 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2846 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2847 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2848 self._table, k, get_pg_type(f)[1])
2851 if not create and k in self._defaults:
2852 if callable(self._defaults[k]):
2853 default = self._defaults[k](self, cr, 1, context)
2855 default = self._defaults[k]
2857 ss = self._columns[k]._symbol_set
2858 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2859 cr.execute(query, (ss[1](default),))
2861 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2863 if isinstance(f, fields.function):
2865 if f.store is not True:
2866 order = f.store[f.store.keys()[0]][2]
2867 todo_update_store.append((order, f, k))
2869 # and add constraints if needed
2870 if isinstance(f, fields.many2one):
2871 if not self.pool.get(f._obj):
2872 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2873 ref = self.pool.get(f._obj)._table
2874 # ref = f._obj.replace('.', '_')
2875 # ir_actions is inherited so foreign key doesn't work on it
2876 if ref != 'ir_actions':
2877 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2878 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2879 self._table, k, ref, f.ondelete)
2881 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2885 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2886 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2889 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2890 "Try to re-run: openerp-server --update=module\n"\
2891 "If it doesn't work, update records and execute manually:\n"\
2892 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2893 self.__logger.warn(msg, k, self._table, self._table, k)
2895 for order, f, k in todo_update_store:
2896 todo_end.append((order, self._update_store, (f, k)))
2899 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2900 create = not bool(cr.fetchone())
2902 cr.commit() # start a new transaction
2904 for (key, con, _) in self._sql_constraints:
2905 conname = '%s_%s' % (self._table, key)
2907 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2908 existing_constraints = cr.dictfetchall()
2913 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2914 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2915 self._table, conname, con),
2916 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2921 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2922 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2923 '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" % (
2929 if not existing_constraints:
2930 # constraint does not exists:
2931 sql_actions['add']['execute'] = True
2932 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2933 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2934 # constraint exists but its definition has changed:
2935 sql_actions['drop']['execute'] = True
2936 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2937 sql_actions['add']['execute'] = True
2938 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2940 # we need to add the constraint:
2941 sql_actions = [item for item in sql_actions.values()]
2942 sql_actions.sort(key=lambda x: x['order'])
2943 for sql_action in [action for action in sql_actions if action['execute']]:
2945 cr.execute(sql_action['query'])
2947 self.__schema.debug(sql_action['msg_ok'])
2949 self.__schema.warn(sql_action['msg_err'])
2953 if hasattr(self, "_sql"):
2954 for line in self._sql.split(';'):
2955 line2 = line.replace('\n', '').strip()
2960 self._parent_store_compute(cr)
2965 def createInstance(cls, pool, cr):
2966 return cls.makeInstance(pool, cr, ['_columns', '_defaults',
2967 '_inherits', '_constraints', '_sql_constraints'])
2969 def __init__(self, pool, cr):
2972 - copy the stored fields' functions in the osv_pool,
2973 - update the _columns with the fields found in ir_model_fields,
2974 - ensure there is a many2one for each _inherits'd parent,
2975 - update the children's _columns,
2976 - give a chance to each field to initialize itself.
2979 super(orm, self).__init__(pool, cr)
2981 if not hasattr(self, '_log_access'):
2982 # if not access is not specify, it is the same value as _auto
2983 self._log_access = getattr(self, "_auto", True)
2985 self._columns = self._columns.copy()
2986 for store_field in self._columns:
2987 f = self._columns[store_field]
2988 if hasattr(f, 'digits_change'):
2990 if not isinstance(f, fields.function):
2994 if self._columns[store_field].store is True:
2995 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2997 sm = self._columns[store_field].store
2998 for object, aa in sm.items():
3000 (fnct, fields2, order, length) = aa
3002 (fnct, fields2, order) = aa
3005 raise except_orm('Error',
3006 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
3007 self.pool._store_function.setdefault(object, [])
3009 for x, y, z, e, f, l in self.pool._store_function[object]:
3010 if (x==self._name) and (y==store_field) and (e==fields2):
3014 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
3015 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
3017 for (key, _, msg) in self._sql_constraints:
3018 self.pool._sql_error[self._table+'_'+key] = msg
3020 # Load manual fields
3022 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
3024 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
3025 for field in cr.dictfetchall():
3026 if field['name'] in self._columns:
3029 'string': field['field_description'],
3030 'required': bool(field['required']),
3031 'readonly': bool(field['readonly']),
3032 'domain': eval(field['domain']) if field['domain'] else None,
3033 'size': field['size'],
3034 'ondelete': field['on_delete'],
3035 'translate': (field['translate']),
3037 #'select': int(field['select_level'])
3040 if field['ttype'] == 'selection':
3041 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
3042 elif field['ttype'] == 'reference':
3043 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
3044 elif field['ttype'] == 'many2one':
3045 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
3046 elif field['ttype'] == 'one2many':
3047 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
3048 elif field['ttype'] == 'many2many':
3049 _rel1 = field['relation'].replace('.', '_')
3050 _rel2 = field['model'].replace('.', '_')
3051 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
3052 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
3054 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
3055 self._inherits_check()
3056 self._inherits_reload()
3057 if not self._sequence:
3058 self._sequence = self._table + '_id_seq'
3059 for k in self._defaults:
3060 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,)
3061 for f in self._columns:
3062 self._columns[f].restart()
3064 __init__.__doc__ = orm_template.__init__.__doc__ + __init__.__doc__
3067 # Update objects that uses this one to update their _inherits fields
3070 def _inherits_reload_src(self):
3071 for obj in self.pool.models.values():
3072 if self._name in obj._inherits:
3073 obj._inherits_reload()
3075 def _inherits_reload(self):
3077 for table in self._inherits:
3078 res.update(self.pool.get(table)._inherit_fields)
3079 for col in self.pool.get(table)._columns.keys():
3080 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
3081 for col in self.pool.get(table)._inherit_fields.keys():
3082 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
3083 self._inherit_fields = res
3084 self._inherits_reload_src()
3086 def _inherits_check(self):
3087 for table, field_name in self._inherits.items():
3088 if field_name not in self._columns:
3089 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
3090 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
3091 required=True, ondelete="cascade")
3092 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
3093 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))
3094 self._columns[field_name].required = True
3095 self._columns[field_name].ondelete = "cascade"
3097 #def __getattr__(self, name):
3099 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
3100 # (though inherits doesn't use Python inheritance).
3101 # Handles translating between local ids and remote ids.
3102 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
3103 # when you have inherits.
3105 # for model, field in self._inherits.iteritems():
3106 # proxy = self.pool.get(model)
3107 # if hasattr(proxy, name):
3108 # attribute = getattr(proxy, name)
3109 # if not hasattr(attribute, '__call__'):
3113 # return super(orm, self).__getattr__(name)
3115 # def _proxy(cr, uid, ids, *args, **kwargs):
3116 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
3117 # lst = [obj[field].id for obj in objects if obj[field]]
3118 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
3123 def fields_get(self, cr, user, fields=None, context=None):
3125 Get the description of list of fields
3127 :param cr: database cursor
3128 :param user: current user id
3129 :param fields: list of fields
3130 :param context: context arguments, like lang, time zone
3131 :return: dictionary of field dictionaries, each one describing a field of the business object
3132 :raise AccessError: * if user has no create/write rights on the requested object
3135 ira = self.pool.get('ir.model.access')
3136 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
3137 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
3138 return super(orm, self).fields_get(cr, user, fields, context, write_access)
3140 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
3143 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3145 fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
3146 if isinstance(ids, (int, long)):
3150 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
3151 result = self._read_flat(cr, user, select, fields, context, load)
3154 for key, v in r.items():
3158 if isinstance(ids, (int, long, dict)):
3159 return result and result[0] or False
3162 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
3167 if fields_to_read == None:
3168 fields_to_read = self._columns.keys()
3170 # Construct a clause for the security rules.
3171 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3172 # or will at least contain self._table.
3173 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3175 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
3176 fields_pre = [f for f in fields_to_read if
3177 f == self.CONCURRENCY_CHECK_FIELD
3178 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3179 ] + self._inherits.values()
3183 def convert_field(f):
3184 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
3185 if f in ('create_date', 'write_date'):
3186 return "date_trunc('second', %s) as %s" % (f_qual, f)
3187 if f == self.CONCURRENCY_CHECK_FIELD:
3188 if self._log_access:
3189 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
3190 return "now()::timestamp AS %s" % (f,)
3191 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
3192 return 'length(%s) as "%s"' % (f_qual, f)
3195 fields_pre2 = map(convert_field, fields_pre)
3196 order_by = self._parent_order or self._order
3197 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
3198 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3200 query += " AND " + (' OR '.join(rule_clause))
3201 query += " ORDER BY " + order_by
3202 for sub_ids in cr.split_for_in_conditions(ids):
3204 cr.execute(query, [tuple(sub_ids)] + rule_params)
3205 if cr.rowcount != len(sub_ids):
3206 raise except_orm(_('AccessError'),
3207 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3208 % (self._description,))
3210 cr.execute(query, (tuple(sub_ids),))
3211 res.extend(cr.dictfetchall())
3213 res = map(lambda x: {'id': x}, ids)
3215 for f in fields_pre:
3216 if f == self.CONCURRENCY_CHECK_FIELD:
3218 if self._columns[f].translate:
3219 ids = [x['id'] for x in res]
3220 #TODO: optimize out of this loop
3221 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3223 r[f] = res_trans.get(r['id'], False) or r[f]
3225 for table in self._inherits:
3226 col = self._inherits[table]
3227 cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
3230 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3238 if not record[col]: # if the record is deleted from _inherits table?
3240 record.update(res3[record[col]])
3241 if col not in fields_to_read:
3244 # all fields which need to be post-processed by a simple function (symbol_get)
3245 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3248 for f in fields_post:
3249 r[f] = self._columns[f]._symbol_get(r[f])
3250 ids = [x['id'] for x in res]
3252 # all non inherited fields for which the attribute whose name is in load is False
3253 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3255 # Compute POST fields
3257 for f in fields_post:
3258 todo.setdefault(self._columns[f]._multi, [])
3259 todo[self._columns[f]._multi].append(f)
3260 for key, val in todo.items():
3262 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3263 assert res2 is not None, \
3264 'The function field "%s" on the "%s" model returned None\n' \
3265 '(a dictionary was expected).' % (val[0], self._name)
3268 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3269 multi_fields = res2.get(record['id'],{})
3271 record[pos] = multi_fields.get(pos,[])
3274 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3277 record[f] = res2[record['id']]
3282 for field in vals.copy():
3284 if field in self._columns:
3285 fobj = self._columns[field]
3292 for group in groups:
3293 module = group.split(".")[0]
3294 grp = group.split(".")[1]
3295 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", \
3296 (grp, module, 'res.groups', user))
3297 readonly = cr.fetchall()
3298 if readonly[0][0] >= 1:
3301 elif readonly[0][0] == 0:
3307 if type(vals[field]) == type([]):
3309 elif type(vals[field]) == type(0.0):
3311 elif type(vals[field]) == type(''):
3312 vals[field] = '=No Permission='
3317 def perm_read(self, cr, user, ids, context=None, details=True):
3319 Returns some metadata about the given records.
3321 :param details: if True, \*_uid fields are replaced with the name of the user
3322 :return: list of ownership dictionaries for each requested record
3323 :rtype: list of dictionaries with the following keys:
3326 * create_uid: user who created the record
3327 * create_date: date when the record was created
3328 * write_uid: last user who changed the record
3329 * write_date: date of the last change to the record
3330 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3337 uniq = isinstance(ids, (int, long))
3341 if self._log_access:
3342 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3343 quoted_table = '"%s"' % self._table
3344 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3345 query = '''SELECT %s, __imd.module, __imd.name
3346 FROM %s LEFT JOIN ir_model_data __imd
3347 ON (__imd.model = %%s and __imd.res_id = %s.id)
3348 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3349 cr.execute(query, (self._name, tuple(ids)))
3350 res = cr.dictfetchall()
3353 r[key] = r[key] or False
3354 if details and key in ('write_uid', 'create_uid') and r[key]:
3356 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3358 pass # Leave the numeric uid there
3359 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3360 del r['name'], r['module']
3365 def _check_concurrency(self, cr, ids, context):
3368 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3370 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3371 for sub_ids in cr.split_for_in_conditions(ids):
3374 id_ref = "%s,%s" % (self._name, id)
3375 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3377 ids_to_check.extend([id, update_date])
3378 if not ids_to_check:
3380 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3383 # mention the first one only to keep the error message readable
3384 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3386 def check_access_rule(self, cr, uid, ids, operation, context=None):
3387 """Verifies that the operation given by ``operation`` is allowed for the user
3388 according to ir.rules.
3390 :param operation: one of ``write``, ``unlink``
3391 :raise except_orm: * if current ir.rules do not permit this operation.
3392 :return: None if the operation is allowed
3394 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3396 where_clause = ' and ' + ' and '.join(where_clause)
3397 for sub_ids in cr.split_for_in_conditions(ids):
3398 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3399 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3400 [sub_ids] + where_params)
3401 if cr.rowcount != len(sub_ids):
3402 raise except_orm(_('AccessError'),
3403 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3404 % (operation, self._description))
3406 def unlink(self, cr, uid, ids, context=None):
3408 Delete records with given ids
3410 :param cr: database cursor
3411 :param uid: current user id
3412 :param ids: id or list of ids
3413 :param context: (optional) context arguments, like lang, time zone
3415 :raise AccessError: * if user has no unlink rights on the requested object
3416 * if user tries to bypass access rules for unlink on the requested object
3417 :raise UserError: if the record is default property for other records
3422 if isinstance(ids, (int, long)):
3425 result_store = self._store_get_values(cr, uid, ids, None, context)
3427 self._check_concurrency(cr, ids, context)
3429 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3431 properties = self.pool.get('ir.property')
3432 domain = [('res_id', '=', False),
3433 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3435 if properties.search(cr, uid, domain, context=context):
3436 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3438 wf_service = netsvc.LocalService("workflow")
3440 wf_service.trg_delete(uid, self._name, oid, cr)
3443 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3444 pool_model_data = self.pool.get('ir.model.data')
3445 ir_values_obj = self.pool.get('ir.values')
3446 for sub_ids in cr.split_for_in_conditions(ids):
3447 cr.execute('delete from ' + self._table + ' ' \
3448 'where id IN %s', (sub_ids,))
3450 # Removing the ir_model_data reference if the record being deleted is a record created by xml/csv file,
3451 # as these are not connected with real database foreign keys, and would be dangling references.
3452 # Step 1. Calling unlink of ir_model_data only for the affected IDS.
3453 referenced_ids = pool_model_data.search(cr, uid, [('res_id','in',list(sub_ids)),('model','=',self._name)], context=context)
3454 # Step 2. Marching towards the real deletion of referenced records
3455 pool_model_data.unlink(cr, uid, referenced_ids, context=context)
3457 # For the same reason, removing the record relevant to ir_values
3458 ir_value_ids = ir_values_obj.search(cr, uid,
3459 ['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
3462 ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
3464 for order, object, store_ids, fields in result_store:
3465 if object != self._name:
3466 obj = self.pool.get(object)
3467 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3468 rids = map(lambda x: x[0], cr.fetchall())
3470 obj._store_set_values(cr, uid, rids, fields, context)
3477 def write(self, cr, user, ids, vals, context=None):
3479 Update records with given ids with the given field values
3481 :param cr: database cursor
3482 :param user: current user id
3484 :param ids: object id or list of object ids to update according to **vals**
3485 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3486 :type vals: dictionary
3487 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3488 :type context: dictionary
3490 :raise AccessError: * if user has no write rights on the requested object
3491 * if user tries to bypass access rules for write on the requested object
3492 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3493 :raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
3495 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3497 + For a many2many field, a list of tuples is expected.
3498 Here is the list of tuple that are accepted, with the corresponding semantics ::
3500 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3501 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3502 (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)
3503 (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)
3504 (4, ID) link to existing record with id = ID (adds a relationship)
3505 (5) unlink all (like using (3,ID) for all linked records)
3506 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3509 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3511 + For a one2many field, a lits of tuples is expected.
3512 Here is the list of tuple that are accepted, with the corresponding semantics ::
3514 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3515 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3516 (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)
3519 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3521 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3522 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3526 for field in vals.copy():
3528 if field in self._columns:
3529 fobj = self._columns[field]
3530 elif field in self._inherit_fields:
3531 fobj = self._inherit_fields[field][2]
3538 for group in groups:
3539 module = group.split(".")[0]
3540 grp = group.split(".")[1]
3541 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", \
3542 (grp, module, 'res.groups', user))
3543 readonly = cr.fetchall()
3544 if readonly[0][0] >= 1:
3547 elif readonly[0][0] == 0:
3559 if isinstance(ids, (int, long)):
3562 self._check_concurrency(cr, ids, context)
3563 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3565 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3567 # No direct update of parent_left/right
3568 vals.pop('parent_left', None)
3569 vals.pop('parent_right', None)
3571 parents_changed = []
3572 parent_order = self._parent_order or self._order
3573 if self._parent_store and (self._parent_name in vals):
3574 # The parent_left/right computation may take up to
3575 # 5 seconds. No need to recompute the values if the
3576 # parent is the same.
3577 # Note: to respect parent_order, nodes must be processed in
3578 # order, so ``parents_changed`` must be ordered properly.
3579 parent_val = vals[self._parent_name]
3581 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL) ORDER BY %s" % \
3582 (self._table, self._parent_name, self._parent_name, parent_order)
3583 cr.execute(query, (tuple(ids), parent_val))
3585 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \
3586 (self._table, self._parent_name, parent_order)
3587 cr.execute(query, (tuple(ids),))
3588 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3595 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3597 if field in self._columns:
3598 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3599 if (not totranslate) or not self._columns[field].translate:
3600 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3601 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3602 direct.append(field)
3604 upd_todo.append(field)
3606 updend.append(field)
3607 if field in self._columns \
3608 and hasattr(self._columns[field], 'selection') \
3610 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3612 if self._log_access:
3613 upd0.append('write_uid=%s')
3614 upd0.append('write_date=now()')
3618 self.check_access_rule(cr, user, ids, 'write', context=context)
3619 for sub_ids in cr.split_for_in_conditions(ids):
3620 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3621 'where id IN %s', upd1 + [sub_ids])
3622 if cr.rowcount != len(sub_ids):
3623 raise except_orm(_('AccessError'),
3624 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3629 if self._columns[f].translate:
3630 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3633 # Inserting value to DB
3634 self.write(cr, user, ids, {f: vals[f]})
3635 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3638 # call the 'set' method of fields which are not classic_write
3639 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3641 # default element in context must be removed when call a one2many or many2many
3642 rel_context = context.copy()
3643 for c in context.items():
3644 if c[0].startswith('default_'):
3645 del rel_context[c[0]]
3647 for field in upd_todo:
3649 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3651 for table in self._inherits:
3652 col = self._inherits[table]
3654 for sub_ids in cr.split_for_in_conditions(ids):
3655 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3656 'where id IN %s', (sub_ids,))
3657 nids.extend([x[0] for x in cr.fetchall()])
3661 if self._inherit_fields[val][0] == table:
3664 self.pool.get(table).write(cr, user, nids, v, context)
3666 self._validate(cr, user, ids, context)
3668 # TODO: use _order to set dest at the right position and not first node of parent
3669 # We can't defer parent_store computation because the stored function
3670 # fields that are computer may refer (directly or indirectly) to
3671 # parent_left/right (via a child_of domain)
3674 self.pool._init_parent[self._name] = True
3676 order = self._parent_order or self._order
3677 parent_val = vals[self._parent_name]
3679 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3681 clause, params = '%s IS NULL' % (self._parent_name,), ()
3683 for id in parents_changed:
3684 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3685 pleft, pright = cr.fetchone()
3686 distance = pright - pleft + 1
3688 # Positions of current siblings, to locate proper insertion point;
3689 # this can _not_ be fetched outside the loop, as it needs to be refreshed
3690 # after each update, in case several nodes are sequentially inserted one
3691 # next to the other (i.e computed incrementally)
3692 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, parent_order), params)
3693 parents = cr.fetchall()
3695 # Find Position of the element
3697 for (parent_pright, parent_id) in parents:
3700 position = parent_pright + 1
3702 # It's the first node of the parent
3707 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3708 position = cr.fetchone()[0] + 1
3710 if pleft < position <= pright:
3711 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3713 if pleft < position:
3714 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3715 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3716 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))
3718 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3719 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3720 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))
3722 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3726 for order, object, ids_to_update, fields_to_recompute in result:
3727 key = (object, tuple(fields_to_recompute))
3728 done.setdefault(key, {})
3729 # avoid to do several times the same computation
3731 for id in ids_to_update:
3732 if id not in done[key]:
3733 done[key][id] = True
3735 self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
3737 wf_service = netsvc.LocalService("workflow")
3739 wf_service.trg_write(user, self._name, id, cr)
3743 # TODO: Should set perm to user.xxx
3745 def create(self, cr, user, vals, context=None):
3747 Create a new record for the model.
3749 The values for the new record are initialized using the ``vals``
3750 argument, and if necessary the result of ``default_get()``.
3752 :param cr: database cursor
3753 :param user: current user id
3755 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3756 :type vals: dictionary
3757 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3758 :type context: dictionary
3759 :return: id of new record created
3760 :raise AccessError: * if user has no create rights on the requested object
3761 * if user tries to bypass access rules for create on the requested object
3762 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3763 :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)
3765 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3766 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3772 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3774 vals = self._add_missing_default_values(cr, user, vals, context)
3777 for v in self._inherits:
3778 if self._inherits[v] not in vals:
3781 tocreate[v] = {'id': vals[self._inherits[v]]}
3782 (upd0, upd1, upd2) = ('', '', [])
3784 for v in vals.keys():
3785 if v in self._inherit_fields:
3786 (table, col, col_detail) = self._inherit_fields[v]
3787 tocreate[table][v] = vals[v]
3790 if (v not in self._inherit_fields) and (v not in self._columns):
3793 # Try-except added to filter the creation of those records whose filds are readonly.
3794 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3796 cr.execute("SELECT nextval('"+self._sequence+"')")
3798 raise except_orm(_('UserError'),
3799 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3801 id_new = cr.fetchone()[0]
3802 for table in tocreate:
3803 if self._inherits[table] in vals:
3804 del vals[self._inherits[table]]
3806 record_id = tocreate[table].pop('id', None)
3808 if record_id is None or not record_id:
3809 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3811 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3813 upd0 += ',' + self._inherits[table]
3815 upd2.append(record_id)
3817 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3818 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3820 for bool_field in bool_fields:
3821 if bool_field not in vals:
3822 vals[bool_field] = False
3824 for field in vals.copy():
3826 if field in self._columns:
3827 fobj = self._columns[field]
3829 fobj = self._inherit_fields[field][2]
3835 for group in groups:
3836 module = group.split(".")[0]
3837 grp = group.split(".")[1]
3838 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" % \
3839 (grp, module, 'res.groups', user))
3840 readonly = cr.fetchall()
3841 if readonly[0][0] >= 1:
3844 elif readonly[0][0] == 0:
3852 if self._columns[field]._classic_write:
3853 upd0 = upd0 + ',"' + field + '"'
3854 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3855 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3857 if not isinstance(self._columns[field], fields.related):
3858 upd_todo.append(field)
3859 if field in self._columns \
3860 and hasattr(self._columns[field], 'selection') \
3862 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3863 if self._log_access:
3864 upd0 += ',create_uid,create_date'
3867 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3868 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3869 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3871 if self._parent_store and not context.get('defer_parent_store_computation'):
3873 self.pool._init_parent[self._name] = True
3875 parent = vals.get(self._parent_name, False)
3877 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3879 result_p = cr.fetchall()
3880 for (pleft,) in result_p:
3885 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3886 pleft_old = cr.fetchone()[0]
3889 cr.execute('select max(parent_right) from '+self._table)
3890 pleft = cr.fetchone()[0] or 0
3891 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3892 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3893 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3895 # default element in context must be remove when call a one2many or many2many
3896 rel_context = context.copy()
3897 for c in context.items():
3898 if c[0].startswith('default_'):
3899 del rel_context[c[0]]
3902 for field in upd_todo:
3903 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3904 self._validate(cr, user, [id_new], context)
3906 if not context.get('no_store_function', False):
3907 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3910 for order, object, ids, fields2 in result:
3911 if not (object, ids, fields2) in done:
3912 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3913 done.append((object, ids, fields2))
3915 if self._log_create and not (context and context.get('no_store_function', False)):
3916 message = self._description + \
3918 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3919 "' " + _("created.")
3920 self.log(cr, user, id_new, message, True, context=context)
3921 wf_service = netsvc.LocalService("workflow")
3922 wf_service.trg_create(user, self._name, id_new, cr)
3925 def _store_get_values(self, cr, uid, ids, fields, context):
3926 """Returns an ordered list of fields.functions to call due to
3927 an update operation on ``fields`` of records with ``ids``,
3928 obtained by calling the 'store' functions of these fields,
3929 as setup by their 'store' attribute.
3931 :return: [(priority, model_name, [record_ids,], [function_fields,])]
3933 # FIXME: rewrite, cleanup, use real variable names
3934 # e.g.: http://pastie.org/1222060
3936 fncts = self.pool._store_function.get(self._name, [])
3937 for fnct in range(len(fncts)):
3942 for f in (fields or []):
3943 if f in fncts[fnct][3]:
3949 result.setdefault(fncts[fnct][0], {})
3951 # uid == 1 for accessing objects having rules defined on store fields
3952 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3953 for id in filter(None, ids2):
3954 result[fncts[fnct][0]].setdefault(id, [])
3955 result[fncts[fnct][0]][id].append(fnct)
3957 for object in result:
3959 for id, fnct in result[object].items():
3960 k2.setdefault(tuple(fnct), [])
3961 k2[tuple(fnct)].append(id)
3962 for fnct, id in k2.items():
3963 dict.setdefault(fncts[fnct[0]][4], [])
3964 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3972 def _store_set_values(self, cr, uid, ids, fields, context):
3973 """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
3974 respecting ``multi`` attributes), and stores the resulting values in the database directly."""
3979 if self._log_access:
3980 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3984 field_dict.setdefault(r[0], [])
3985 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3986 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3987 for i in self.pool._store_function.get(self._name, []):
3989 up_write_date = write_date + datetime.timedelta(hours=i[5])
3990 if datetime.datetime.now() < up_write_date:
3992 field_dict[r[0]].append(i[1])
3998 if self._columns[f]._multi not in keys:
3999 keys.append(self._columns[f]._multi)
4000 todo.setdefault(self._columns[f]._multi, [])
4001 todo[self._columns[f]._multi].append(f)
4005 # uid == 1 for accessing objects having rules defined on store fields
4006 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
4007 for id, value in result.items():
4009 for f in value.keys():
4010 if f in field_dict[id]:
4017 if self._columns[v]._type in ('many2one', 'one2one'):
4019 value[v] = value[v][0]
4022 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
4023 upd1.append(self._columns[v]._symbol_set[1](value[v]))
4026 cr.execute('update "' + self._table + '" set ' + \
4027 ','.join(upd0) + ' where id = %s', upd1)
4031 # uid == 1 for accessing objects having rules defined on store fields
4032 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
4033 for r in result.keys():
4035 if r in field_dict.keys():
4036 if f in field_dict[r]:
4038 for id, value in result.items():
4039 if self._columns[f]._type in ('many2one', 'one2one'):
4044 cr.execute('update "' + self._table + '" set ' + \
4045 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
4051 def perm_write(self, cr, user, ids, fields, context=None):
4052 raise NotImplementedError(_('This method does not exist anymore'))
4054 # TODO: ameliorer avec NULL
4055 def _where_calc(self, cr, user, domain, active_test=True, context=None):
4056 """Computes the WHERE clause needed to implement an OpenERP domain.
4057 :param domain: the domain to compute
4059 :param active_test: whether the default filtering of records with ``active``
4060 field set to ``False`` should be applied.
4061 :return: the query expressing the given domain as provided in domain
4062 :rtype: osv.query.Query
4067 # if the object has a field named 'active', filter out all inactive
4068 # records unless they were explicitely asked for
4069 if 'active' in self._columns and (active_test and context.get('active_test', True)):
4071 active_in_args = False
4073 if a[0] == 'active':
4074 active_in_args = True
4075 if not active_in_args:
4076 domain.insert(0, ('active', '=', 1))
4078 domain = [('active', '=', 1)]
4082 e = expression.expression(domain)
4083 e.parse(cr, user, self, context)
4084 tables = e.get_tables()
4085 where_clause, where_params = e.to_sql()
4086 where_clause = where_clause and [where_clause] or []
4088 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
4090 return Query(tables, where_clause, where_params)
4092 def _check_qorder(self, word):
4093 if not regex_order.match(word):
4094 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)'))
4097 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
4098 """Add what's missing in ``query`` to implement all appropriate ir.rules
4099 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
4101 :param query: the current query object
4103 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
4105 if parent_model and child_object:
4106 # as inherited rules are being applied, we need to add the missing JOIN
4107 # to reach the parent table (if it was not JOINed yet in the query)
4108 child_object._inherits_join_add(parent_model, query)
4109 query.where_clause += added_clause
4110 query.where_clause_params += added_params
4111 for table in added_tables:
4112 if table not in query.tables:
4113 query.tables.append(table)
4117 # apply main rules on the object
4118 rule_obj = self.pool.get('ir.rule')
4119 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
4121 # apply ir.rules from the parents (through _inherits)
4122 for inherited_model in self._inherits:
4123 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
4124 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
4126 def _generate_m2o_order_by(self, order_field, query):
4128 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
4129 either native m2o fields or function/related fields that are stored, including
4130 intermediate JOINs for inheritance if required.
4132 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
4134 if order_field not in self._columns and order_field in self._inherit_fields:
4135 # also add missing joins for reaching the table containing the m2o field
4136 qualified_field = self._inherits_join_calc(order_field, query)
4137 order_field_column = self._inherit_fields[order_field][2]
4139 qualified_field = '"%s"."%s"' % (self._table, order_field)
4140 order_field_column = self._columns[order_field]
4142 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
4143 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
4144 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
4145 "to be used as ordering fields! Ignoring sorting for %s.%s",
4146 self._name, order_field)
4149 # figure out the applicable order_by for the m2o
4150 dest_model = self.pool.get(order_field_column._obj)
4151 m2o_order = dest_model._order
4152 if not regex_order.match(m2o_order):
4153 # _order is complex, can't use it here, so we default to _rec_name
4154 m2o_order = dest_model._rec_name
4156 # extract the field names, to be able to qualify them and add desc/asc
4158 for order_part in m2o_order.split(","):
4159 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
4160 m2o_order = m2o_order_list
4162 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
4163 # as we don't want to exclude results that have NULL values for the m2o
4164 src_table, src_field = qualified_field.replace('"','').split('.', 1)
4165 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
4166 qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
4167 return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
4170 def _generate_order_by(self, order_spec, query):
4172 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
4173 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
4175 :raise" except_orm in case order_spec is malformed
4177 order_by_clause = self._order
4179 order_by_elements = []
4180 self._check_qorder(order_spec)
4181 for order_part in order_spec.split(','):
4182 order_split = order_part.strip().split(' ')
4183 order_field = order_split[0].strip()
4184 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
4186 if order_field == 'id':
4187 order_by_clause = '"%s"."%s"' % (self._table, order_field)
4188 elif order_field in self._columns:
4189 order_column = self._columns[order_field]
4190 if order_column._classic_read:
4191 inner_clause = '"%s"."%s"' % (self._table, order_field)
4192 elif order_column._type == 'many2one':
4193 inner_clause = self._generate_m2o_order_by(order_field, query)
4195 continue # ignore non-readable or "non-joinable" fields
4196 elif order_field in self._inherit_fields:
4197 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4198 order_column = parent_obj._columns[order_field]
4199 if order_column._classic_read:
4200 inner_clause = self._inherits_join_calc(order_field, query)
4201 elif order_column._type == 'many2one':
4202 inner_clause = self._generate_m2o_order_by(order_field, query)
4204 continue # ignore non-readable or "non-joinable" fields
4206 if isinstance(inner_clause, list):
4207 for clause in inner_clause:
4208 order_by_elements.append("%s %s" % (clause, order_direction))
4210 order_by_elements.append("%s %s" % (inner_clause, order_direction))
4211 if order_by_elements:
4212 order_by_clause = ",".join(order_by_elements)
4214 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4216 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4218 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4219 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4220 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4221 This is ok at the security level because this method is private and not callable through XML-RPC.
4223 :param access_rights_uid: optional user ID to use when checking access rights
4224 (not for ir.rules, this is only for ir.model.access)
4228 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4230 query = self._where_calc(cr, user, args, context=context)
4231 self._apply_ir_rules(cr, user, query, 'read', context=context)
4232 order_by = self._generate_order_by(order, query)
4233 from_clause, where_clause, where_clause_params = query.get_sql()
4235 limit_str = limit and ' limit %d' % limit or ''
4236 offset_str = offset and ' offset %d' % offset or ''
4237 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4240 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4243 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4245 return [x[0] for x in res]
4247 # returns the different values ever entered for one field
4248 # this is used, for example, in the client when the user hits enter on
4250 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4253 if field in self._inherit_fields:
4254 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4256 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4258 def copy_data(self, cr, uid, id, default=None, context=None):
4260 Copy given record's data with all its fields values
4262 :param cr: database cursor
4263 :param user: current user id
4264 :param id: id of the record to copy
4265 :param default: field values to override in the original values of the copied record
4266 :type default: dictionary
4267 :param context: context arguments, like lang, time zone
4268 :type context: dictionary
4269 :return: dictionary containing all the field values
4275 # avoid recursion through already copied records in case of circular relationship
4276 seen_map = context.setdefault('__copy_data_seen',{})
4277 if id in seen_map.setdefault(self._name,[]):
4279 seen_map[self._name].append(id)
4283 if 'state' not in default:
4284 if 'state' in self._defaults:
4285 if callable(self._defaults['state']):
4286 default['state'] = self._defaults['state'](self, cr, uid, context)
4288 default['state'] = self._defaults['state']
4290 context_wo_lang = context.copy()
4291 if 'lang' in context:
4292 del context_wo_lang['lang']
4293 data = self.read(cr, uid, [id,], context=context_wo_lang)
4297 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4299 fields = self.fields_get(cr, uid, context=context)
4301 ftype = fields[f]['type']
4303 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4307 data[f] = default[f]
4308 elif 'function' in fields[f]:
4310 elif ftype == 'many2one':
4312 data[f] = data[f] and data[f][0]
4315 elif ftype in ('one2many', 'one2one'):
4317 rel = self.pool.get(fields[f]['relation'])
4319 # duplicate following the order of the ids
4320 # because we'll rely on it later for copying
4321 # translations in copy_translation()!
4323 for rel_id in data[f]:
4324 # the lines are first duplicated using the wrong (old)
4325 # parent but then are reassigned to the correct one thanks
4326 # to the (0, 0, ...)
4327 d = rel.copy_data(cr, uid, rel_id, context=context)
4329 res.append((0, 0, d))
4331 elif ftype == 'many2many':
4332 data[f] = [(6, 0, data[f])]
4336 # make sure we don't break the current parent_store structure and
4337 # force a clean recompute!
4338 for parent_column in ['parent_left', 'parent_right']:
4339 data.pop(parent_column, None)
4341 for v in self._inherits:
4342 del data[self._inherits[v]]
4345 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4349 # avoid recursion through already copied records in case of circular relationship
4350 seen_map = context.setdefault('__copy_translations_seen',{})
4351 if old_id in seen_map.setdefault(self._name,[]):
4353 seen_map[self._name].append(old_id)
4355 trans_obj = self.pool.get('ir.translation')
4356 fields = self.fields_get(cr, uid, context=context)
4358 translation_records = []
4359 for field_name, field_def in fields.items():
4360 # we must recursively copy the translations for o2o and o2m
4361 if field_def['type'] in ('one2one', 'one2many'):
4362 target_obj = self.pool.get(field_def['relation'])
4363 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4364 # here we rely on the order of the ids to match the translations
4365 # as foreseen in copy_data()
4366 old_children = sorted(old_record[field_name])
4367 new_children = sorted(new_record[field_name])
4368 for (old_child, new_child) in zip(old_children, new_children):
4369 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4370 # and for translatable fields we keep them for copy
4371 elif field_def.get('translate'):
4373 if field_name in self._columns:
4374 trans_name = self._name + "," + field_name
4375 elif field_name in self._inherit_fields:
4376 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4378 trans_ids = trans_obj.search(cr, uid, [
4379 ('name', '=', trans_name),
4380 ('res_id', '=', old_id)
4382 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4384 for record in translation_records:
4386 record['res_id'] = new_id
4387 trans_obj.create(cr, uid, record, context=context)
4390 def copy(self, cr, uid, id, default=None, context=None):
4392 Duplicate record with given id updating it with default values
4394 :param cr: database cursor
4395 :param uid: current user id
4396 :param id: id of the record to copy
4397 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4398 :type default: dictionary
4399 :param context: context arguments, like lang, time zone
4400 :type context: dictionary
4406 context = context.copy()
4407 data = self.copy_data(cr, uid, id, default, context)
4408 new_id = self.create(cr, uid, data, context)
4409 self.copy_translations(cr, uid, id, new_id, context)
4412 def exists(self, cr, uid, ids, context=None):
4413 if type(ids) in (int, long):
4415 query = 'SELECT count(1) FROM "%s"' % (self._table)
4416 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4417 return cr.fetchone()[0] == len(ids)
4419 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4420 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4421 self._name, DeprecationWarning, stacklevel=3)
4422 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4423 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4424 return self._check_recursion(cr, uid, ids, context, parent)
4426 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4428 Verifies that there is no loop in a hierarchical structure of records,
4429 by following the parent relationship using the **parent** field until a loop
4430 is detected or until a top-level record is found.
4432 :param cr: database cursor
4433 :param uid: current user id
4434 :param ids: list of ids of records to check
4435 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4436 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4440 parent = self._parent_name
4442 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4445 for i in range(0, len(ids), cr.IN_MAX):
4446 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4447 cr.execute(query, (tuple(sub_ids_parent),))
4448 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4449 ids_parent = ids_parent2
4450 for i in ids_parent:
4455 def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
4456 """Find out the XML ID(s) of any database record.
4458 **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
4460 :return: map of ids to the list of their fully qualified XML IDs
4461 (empty list when there's none).
4463 model_data_obj = self.pool.get('ir.model.data')
4464 data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
4465 data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
4468 # can't use dict.fromkeys() as the list would be shared!
4470 for record in data_results:
4471 result[record['res_id']].append('%(module)s.%(name)s' % record)
4474 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4475 """Find out the XML ID of any database record, if there
4476 is one. This method works as a possible implementation
4477 for a function field, to be able to add it to any
4478 model object easily, referencing it as ``osv.osv.get_xml_id``.
4480 When multiple XML IDs exist for a record, only one
4481 of them is returned (randomly).
4483 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4485 :return: map of ids to their fully qualified XML ID,
4486 defaulting to an empty string when there's none
4487 (to be usable as a function field).
4489 results = self._get_xml_ids(cr, uid, ids)
4490 for k, v in results.items():
4497 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: