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)
833 fields_export = fields_to_export + []
837 for row in self.browse(cr, uid, ids, context):
838 datas += self.__export_row(cr, uid, row, fields_to_export, context)
839 return {'datas': datas}
841 def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
843 Import given data in given module
845 :param cr: database cursor
846 :param uid: current user id
847 :param fields: list of fields
848 :param data: data to import
849 :param mode: 'init' or 'update' for record creation
850 :param current_module: module name
851 :param noupdate: flag for record creation
852 :param context: context arguments, like lang, time zone,
853 :param filename: optional file to store partial import state for recovery
856 This method is used when importing data via client menu.
858 Example of fields to import for a sale.order::
861 partner_id, (=name_search)
862 order_line/.id, (=database_id)
864 order_line/product_id/id, (=xml id)
865 order_line/price_unit,
866 order_line/product_uom_qty,
867 order_line/product_uom/id (=xml_id)
871 def _replace_field(x):
872 x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
873 return x.replace(':id','/id').split('/')
874 fields = map(_replace_field, fields)
875 logger = netsvc.Logger()
876 ir_model_data_obj = self.pool.get('ir.model.data')
878 # mode: id (XML id) or .id (database id) or False for name_get
879 def _get_id(model_name, id, current_module=False, mode='id'):
882 obj_model = self.pool.get(model_name)
883 ids = obj_model.search(cr, uid, [('id', '=', int(id))])
885 raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
888 module, xml_id = id.rsplit('.', 1)
890 module, xml_id = current_module, id
891 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
892 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
893 if not ir_model_data:
894 raise ValueError('No references to %s.%s' % (module, xml_id))
895 id = ir_model_data[0]['res_id']
897 obj_model = self.pool.get(model_name)
898 ids = obj_model.name_search(cr, uid, id, operator='=', context=context)
900 raise ValueError('No record found for %s' % (id,))
905 # datas: a list of records, each record is defined by a list of values
906 # prefix: a list of prefix fields ['line_ids']
907 # position: the line to process, skip is False if it's the first line of the current record
909 # (res, position, warning, res_id) with
910 # res: the record for the next line to process (including it's one2many)
911 # position: the new position for the next line
912 # res_id: the ID of the record if it's a modification
913 def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
914 line = datas[position]
922 for i in range(len(fields)):
925 raise Exception(_('Please check that all your lines have %d columns.'
926 'Stopped around line %d having %d columns.') % \
927 (len(fields), position+2, len(line)))
932 if field[:len(prefix)] <> prefix:
937 # ID of the record using a XML ID
938 if field[len(prefix)]=='id':
940 data_res_id = _get_id(model_name, line[i], current_module, 'id')
941 except ValueError, e:
946 # ID of the record using a database ID
947 elif field[len(prefix)]=='.id':
948 data_res_id = _get_id(model_name, line[i], current_module, '.id')
951 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
952 if fields_def[field[len(prefix)]]['type']=='one2many':
953 if field[len(prefix)] in done:
955 done[field[len(prefix)]] = True
956 relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
957 newfd = relation_obj.fields_get( cr, uid, context=context )
961 while pos < len(datas):
962 res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
965 (newrow, pos, w2, data_res_id2, xml_id2) = res2
966 nbrmax = max(nbrmax, pos)
969 if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
971 res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
973 elif fields_def[field[len(prefix)]]['type']=='many2one':
974 relation = fields_def[field[len(prefix)]]['relation']
975 if len(field) == len(prefix)+1:
978 mode = field[len(prefix)+1]
979 res = _get_id(relation, line[i], current_module, mode)
981 elif fields_def[field[len(prefix)]]['type']=='many2many':
982 relation = fields_def[field[len(prefix)]]['relation']
983 if len(field) == len(prefix)+1:
986 mode = field[len(prefix)+1]
988 # TODO: improve this by using csv.csv_reader
990 for db_id in line[i].split(config.get('csv_internal_sep')):
991 res.append( _get_id(relation, db_id, current_module, mode) )
994 elif fields_def[field[len(prefix)]]['type'] == 'integer':
995 res = line[i] and int(line[i]) or 0
996 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
997 res = line[i].lower() not in ('0', 'false', 'off')
998 elif fields_def[field[len(prefix)]]['type'] == 'float':
999 res = line[i] and float(line[i]) or 0.0
1000 elif fields_def[field[len(prefix)]]['type'] == 'selection':
1001 for key, val in fields_def[field[len(prefix)]]['selection']:
1002 if tools.ustr(line[i]) in [tools.ustr(key), tools.ustr(val)]:
1005 if line[i] and not res:
1006 logger.notifyChannel("import", netsvc.LOG_WARNING,
1007 _("key '%s' not found in selection field '%s'") % \
1008 (tools.ustr(line[i]), tools.ustr(field[len(prefix)])))
1009 warning += [_("Key/value '%s' not found in selection field '%s'") % (tools.ustr(line[i]), tools.ustr(field[len(prefix)]))]
1014 row[field[len(prefix)]] = res or False
1016 result = (row, nbrmax, warning, data_res_id, xml_id)
1019 fields_def = self.fields_get(cr, uid, context=context)
1021 if config.get('import_partial', False) and filename:
1022 data = pickle.load(file(config.get('import_partial')))
1023 original_value = data.get(filename, 0)
1026 while position<len(datas):
1029 (res, position, warning, res_id, xml_id) = \
1030 process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
1033 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
1036 id = ir_model_data_obj._update(cr, uid, self._name,
1037 current_module, res, mode=mode, xml_id=xml_id,
1038 noupdate=noupdate, res_id=res_id, context=context)
1039 except Exception, e:
1040 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
1042 if config.get('import_partial', False) and filename and (not (position%100)):
1043 data = pickle.load(file(config.get('import_partial')))
1044 data[filename] = position
1045 pickle.dump(data, file(config.get('import_partial'), 'wb'))
1046 if context.get('defer_parent_store_computation'):
1047 self._parent_store_compute(cr)
1050 if context.get('defer_parent_store_computation'):
1051 self._parent_store_compute(cr)
1052 return (position, 0, 0, 0)
1054 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1056 Read records with given ids with the given fields
1058 :param cr: database cursor
1059 :param user: current user id
1060 :param ids: id or list of the ids of the records to read
1061 :param fields: optional list of field names to return (default: all fields would be returned)
1062 :type fields: list (example ['field_name_1', ...])
1063 :param context: optional context dictionary - it may contains keys for specifying certain options
1064 like ``context_lang``, ``context_tz`` to alter the results of the call.
1065 A special ``bin_size`` boolean flag may also be passed in the context to request the
1066 value of all fields.binary columns to be returned as the size of the binary instead of its
1067 contents. This can also be selectively overriden by passing a field-specific flag
1068 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1069 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1070 :return: list of dictionaries((dictionary per record asked)) with requested field values
1071 :rtype: [{‘name_of_the_field’: value, ...}, ...]
1072 :raise AccessError: * if user has no read rights on the requested object
1073 * if user tries to bypass access rules for read on the requested object
1076 raise NotImplementedError(_('The read method is not implemented on this object !'))
1078 def get_invalid_fields(self, cr, uid):
1079 return list(self._invalids)
1081 def _validate(self, cr, uid, ids, context=None):
1082 context = context or {}
1083 lng = context.get('lang', False) or 'en_US'
1084 trans = self.pool.get('ir.translation')
1086 for constraint in self._constraints:
1087 fun, msg, fields = constraint
1088 if not fun(self, cr, uid, ids):
1089 # Check presence of __call__ directly instead of using
1090 # callable() because it will be deprecated as of Python 3.0
1091 if hasattr(msg, '__call__'):
1092 tmp_msg = msg(self, cr, uid, ids, context=context)
1093 if isinstance(tmp_msg, tuple):
1094 tmp_msg, params = tmp_msg
1095 translated_msg = tmp_msg % params
1097 translated_msg = tmp_msg
1099 translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1101 _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1103 self._invalids.update(fields)
1106 raise except_orm('ValidateError', '\n'.join(error_msgs))
1108 self._invalids.clear()
1110 def default_get(self, cr, uid, fields_list, context=None):
1112 Returns default values for the fields in fields_list.
1114 :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1115 :type fields_list: list
1116 :param context: optional context dictionary - it may contains keys for specifying certain options
1117 like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1118 It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1119 or override a default value for a field.
1120 A special ``bin_size`` boolean flag may also be passed in the context to request the
1121 value of all fields.binary columns to be returned as the size of the binary instead of its
1122 contents. This can also be selectively overriden by passing a field-specific flag
1123 in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1124 Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1125 :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1127 # trigger view init hook
1128 self.view_init(cr, uid, fields_list, context)
1134 # get the default values for the inherited fields
1135 for t in self._inherits.keys():
1136 defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1139 # get the default values defined in the object
1140 for f in fields_list:
1141 if f in self._defaults:
1142 if callable(self._defaults[f]):
1143 defaults[f] = self._defaults[f](self, cr, uid, context)
1145 defaults[f] = self._defaults[f]
1147 fld_def = ((f in self._columns) and self._columns[f]) \
1148 or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1151 if isinstance(fld_def, fields.property):
1152 property_obj = self.pool.get('ir.property')
1153 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1155 if isinstance(prop_value, (browse_record, browse_null)):
1156 defaults[f] = prop_value.id
1158 defaults[f] = prop_value
1160 if f not in defaults:
1163 # get the default values set by the user and override the default
1164 # values defined in the object
1165 ir_values_obj = self.pool.get('ir.values')
1166 res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1167 for id, field, field_value in res:
1168 if field in fields_list:
1169 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1170 if fld_def._type in ('many2one', 'one2one'):
1171 obj = self.pool.get(fld_def._obj)
1172 if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1174 if fld_def._type in ('many2many'):
1175 obj = self.pool.get(fld_def._obj)
1177 for i in range(len(field_value)):
1178 if not obj.search(cr, uid, [('id', '=',
1181 field_value2.append(field_value[i])
1182 field_value = field_value2
1183 if fld_def._type in ('one2many'):
1184 obj = self.pool.get(fld_def._obj)
1186 for i in range(len(field_value)):
1187 field_value2.append({})
1188 for field2 in field_value[i]:
1189 if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1190 obj2 = self.pool.get(obj._columns[field2]._obj)
1191 if not obj2.search(cr, uid,
1192 [('id', '=', field_value[i][field2])]):
1194 elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1195 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1196 if not obj2.search(cr, uid,
1197 [('id', '=', field_value[i][field2])]):
1199 # TODO add test for many2many and one2many
1200 field_value2[i][field2] = field_value[i][field2]
1201 field_value = field_value2
1202 defaults[field] = field_value
1204 # get the default values from the context
1205 for key in context or {}:
1206 if key.startswith('default_') and (key[8:] in fields_list):
1207 defaults[key[8:]] = context[key]
1211 def perm_read(self, cr, user, ids, context=None, details=True):
1212 raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1214 def unlink(self, cr, uid, ids, context=None):
1215 raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1217 def write(self, cr, user, ids, vals, context=None):
1218 raise NotImplementedError(_('The write method is not implemented on this object !'))
1220 def create(self, cr, user, vals, context=None):
1221 raise NotImplementedError(_('The create method is not implemented on this object !'))
1223 def fields_get_keys(self, cr, user, context=None):
1224 res = self._columns.keys()
1225 for parent in self._inherits:
1226 res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1229 # returns the definition of each field in the object
1230 # the optional fields parameter can limit the result to some fields
1231 def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1235 translation_obj = self.pool.get('ir.translation')
1236 for parent in self._inherits:
1237 res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1239 if self._columns.keys():
1240 for f in self._columns.keys():
1241 field_col = self._columns[f]
1242 if allfields and f not in allfields:
1244 res[f] = {'type': field_col._type}
1245 # This additional attributes for M2M and function field is added
1246 # because we need to display tooltip with this additional information
1247 # when client is started in debug mode.
1248 if isinstance(field_col, fields.function):
1249 res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1250 res[f]['store'] = field_col.store
1251 if isinstance(field_col.store, dict):
1252 res[f]['store'] = str(field_col.store)
1253 res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1254 res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1255 res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1256 res[f]['func_obj'] = field_col._obj or False
1257 res[f]['func_method'] = field_col._method
1258 if isinstance(field_col, fields.many2many):
1259 res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1260 res[f]['third_table'] = field_col._rel
1261 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1262 'change_default', 'translate', 'help', 'select', 'selectable'):
1263 if getattr(field_col, arg):
1264 res[f][arg] = getattr(field_col, arg)
1265 if not write_access:
1266 res[f]['readonly'] = True
1267 res[f]['states'] = {}
1268 for arg in ('digits', 'invisible', 'filters'):
1269 if getattr(field_col, arg, None):
1270 res[f][arg] = getattr(field_col, arg)
1272 if field_col.string:
1273 res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1275 res[f]['string'] = res_trans
1277 help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1279 res[f]['help'] = help_trans
1281 if hasattr(field_col, 'selection'):
1282 if isinstance(field_col.selection, (tuple, list)):
1283 sel = field_col.selection
1284 # translate each selection option
1286 for (key, val) in sel:
1289 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1290 sel2.append((key, val2 or val))
1292 res[f]['selection'] = sel
1294 # call the 'dynamic selection' function
1295 res[f]['selection'] = field_col.selection(self, cr, user, context)
1296 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1297 res[f]['relation'] = field_col._obj
1298 res[f]['domain'] = field_col._domain
1299 res[f]['context'] = field_col._context
1301 #TODO : read the fields from the database
1305 # filter out fields which aren't in the fields list
1306 for r in res.keys():
1307 if r not in allfields:
1312 # Overload this method if you need a window title which depends on the context
1314 def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1317 def __view_look_dom(self, cr, user, node, view_id, context=None):
1325 if isinstance(s, unicode):
1326 return s.encode('utf8')
1329 # return True if node can be displayed to current user
1330 def check_group(node):
1331 if node.get('groups'):
1332 groups = node.get('groups').split(',')
1333 access_pool = self.pool.get('ir.model.access')
1334 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1336 node.set('invisible', '1')
1337 if 'attrs' in node.attrib:
1338 del(node.attrib['attrs']) #avoid making field visible later
1339 del(node.attrib['groups'])
1344 if node.tag in ('field', 'node', 'arrow'):
1345 if node.get('object'):
1350 if f.tag in ('field'):
1351 xml += etree.tostring(f, encoding="utf-8")
1353 new_xml = etree.fromstring(encode(xml))
1354 ctx = context.copy()
1355 ctx['base_model_name'] = self._name
1356 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1361 attrs = {'views': views}
1363 if node.get('name'):
1366 if node.get('name') in self._columns:
1367 column = self._columns[node.get('name')]
1369 column = self._inherit_fields[node.get('name')][2]
1374 relation = self.pool.get(column._obj)
1379 if f.tag in ('form', 'tree', 'graph'):
1381 ctx = context.copy()
1382 ctx['base_model_name'] = self._name
1383 xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1384 views[str(f.tag)] = {
1388 attrs = {'views': views}
1389 if node.get('widget') and node.get('widget') == 'selection':
1390 # Prepare the cached selection list for the client. This needs to be
1391 # done even when the field is invisible to the current user, because
1392 # other events could need to change its value to any of the selectable ones
1393 # (such as on_change events, refreshes, etc.)
1395 # If domain and context are strings, we keep them for client-side, otherwise
1396 # we evaluate them server-side to consider them when generating the list of
1398 # TODO: find a way to remove this hack, by allow dynamic domains
1400 if column._domain and not isinstance(column._domain, basestring):
1401 dom = column._domain
1402 dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1403 search_context = dict(context)
1404 if column._context and not isinstance(column._context, basestring):
1405 search_context.update(column._context)
1406 attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1407 if (node.get('required') and not int(node.get('required'))) or not column.required:
1408 attrs['selection'].append((False, ''))
1409 fields[node.get('name')] = attrs
1411 elif node.tag in ('form', 'tree'):
1412 result = self.view_header_get(cr, user, False, node.tag, context)
1414 node.set('string', result)
1416 elif node.tag == 'calendar':
1417 for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1418 if node.get(additional_field):
1419 fields[node.get(additional_field)] = {}
1421 if 'groups' in node.attrib:
1425 if ('lang' in context) and not result:
1426 if node.get('string'):
1427 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1428 if trans == node.get('string') and ('base_model_name' in context):
1429 # If translation is same as source, perhaps we'd have more luck with the alternative model name
1430 # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1431 trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1433 node.set('string', trans)
1434 if node.get('confirm'):
1435 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1437 node.set('confirm', trans)
1439 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1441 node.set('sum', trans)
1442 if node.get('help'):
1443 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('help'))
1445 node.set('help', trans)
1448 if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1449 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1453 def _disable_workflow_buttons(self, cr, user, node):
1455 # admin user can always activate workflow buttons
1458 # TODO handle the case of more than one workflow for a model or multiple
1459 # transitions with different groups and same signal
1460 usersobj = self.pool.get('res.users')
1461 buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1462 for button in buttons:
1463 user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1464 cr.execute("""SELECT DISTINCT t.group_id
1466 INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1467 INNER JOIN wkf_transition t ON (t.act_to = a.id)
1470 AND t.group_id is NOT NULL
1471 """, (self._name, button.get('name')))
1472 group_ids = [x[0] for x in cr.fetchall() if x[0]]
1473 can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1474 button.set('readonly', str(int(not can_click)))
1477 def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1478 fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1479 node = self._disable_workflow_buttons(cr, user, node)
1480 arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1482 if node.tag == 'diagram':
1483 if node.getchildren()[0].tag == 'node':
1484 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1485 if node.getchildren()[1].tag == 'arrow':
1486 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1487 for key, value in node_fields.items():
1489 for key, value in arrow_fields.items():
1492 fields = self.fields_get(cr, user, fields_def.keys(), context)
1493 for field in fields_def:
1495 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1496 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1497 elif field in fields:
1498 fields[field].update(fields_def[field])
1500 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))
1501 res = cr.fetchall()[:]
1503 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1504 msg = "\n * ".join([r[0] for r in res])
1505 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1506 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1507 raise except_orm('View error', msg)
1510 def __get_default_calendar_view(self):
1511 """Generate a default calendar view (For internal use only).
1514 arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1515 '<calendar string="%s"') % (self._description)
1517 if (self._date_name not in self._columns):
1519 for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1520 if dt in self._columns:
1521 self._date_name = dt
1526 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1529 arch += ' date_start="%s"' % (self._date_name)
1531 for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1532 if color in self._columns:
1533 arch += ' color="' + color + '"'
1536 dt_stop_flag = False
1538 for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1539 if dt_stop in self._columns:
1540 arch += ' date_stop="' + dt_stop + '"'
1544 if not dt_stop_flag:
1545 for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1546 if dt_delay in self._columns:
1547 arch += ' date_delay="' + dt_delay + '"'
1551 ' <field name="%s"/>\n'
1552 '</calendar>') % (self._rec_name)
1556 def __get_default_search_view(self, cr, uid, context=None):
1557 form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1558 tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1560 fields_to_search = set()
1561 fields = self.fields_get(cr, uid, context=context)
1562 for field in fields:
1563 if fields[field].get('select'):
1564 fields_to_search.add(field)
1565 for view in (form_view, tree_view):
1566 view_root = etree.fromstring(view['arch'])
1567 # Only care about select=1 in xpath below, because select=2 is covered
1568 # by the custom advanced search in clients
1569 fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1571 tree_view_root = view_root # as provided by loop above
1572 search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1573 field_group = etree.Element("group")
1574 search_view.append(field_group)
1576 for field_name in fields_to_search:
1577 field_group.append(etree.Element("field", attrib={'name': field_name}))
1579 return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1582 # if view_id, view_type is not required
1584 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1586 Get the detailed composition of the requested view like fields, model, view architecture
1588 :param cr: database cursor
1589 :param user: current user id
1590 :param view_id: id of the view or None
1591 :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1592 :param context: context arguments, like lang, time zone
1593 :param toolbar: true to include contextual actions
1594 :param submenu: example (portal_project module)
1595 :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1596 :raise AttributeError:
1597 * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1598 * if some tag other than 'position' is found in parent view
1599 :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1606 if isinstance(s, unicode):
1607 return s.encode('utf8')
1610 def raise_view_error(error_msg, child_view_id):
1611 view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1612 raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1613 % { 'xml_id': child_view.xml_id,
1614 'parent_xml_id': view.xml_id,
1615 'model': self._name, })
1617 def _inherit_apply(src, inherit, inherit_id=None):
1618 def _find(node, node2):
1619 if node2.tag == 'xpath':
1620 res = node.xpath(node2.get('expr'))
1626 for n in node.getiterator(node2.tag):
1628 if node2.tag == 'field':
1629 # only compare field names, a field can be only once in a given view
1630 # at a given level (and for multilevel expressions, we should use xpath
1631 # inheritance spec anyway)
1632 if node2.get('name') == n.get('name'):
1636 for attr in node2.attrib:
1637 if attr == 'position':
1640 if n.get(attr) == node2.get(attr):
1647 # End: _find(node, node2)
1649 doc_dest = etree.fromstring(encode(inherit))
1650 toparse = [doc_dest]
1653 node2 = toparse.pop(0)
1654 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1656 if node2.tag == 'data':
1657 toparse += [ c for c in doc_dest ]
1659 node = _find(src, node2)
1660 if node is not None:
1662 if node2.get('position'):
1663 pos = node2.get('position')
1664 if pos == 'replace':
1665 parent = node.getparent()
1667 src = copy.deepcopy(node2[0])
1670 node.addprevious(child)
1671 node.getparent().remove(node)
1672 elif pos == 'attributes':
1673 for child in node2.getiterator('attribute'):
1674 attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1676 node.set(attribute[0], attribute[1])
1678 del(node.attrib[attribute[0]])
1680 sib = node.getnext()
1684 elif pos == 'after':
1689 sib.addprevious(child)
1690 elif pos == 'before':
1691 node.addprevious(child)
1693 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1696 ' %s="%s"' % (attr, node2.get(attr))
1697 for attr in node2.attrib
1698 if attr != 'position'
1700 tag = "<%s%s>" % (node2.tag, attrs)
1701 raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1703 # End: _inherit_apply(src, inherit)
1705 result = {'type': view_type, 'model': self._name}
1710 parent_view_model = None
1712 view_ref = context.get(view_type + '_view_ref', False)
1713 if view_ref and not view_id:
1715 module, view_ref = view_ref.split('.', 1)
1716 cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1717 view_ref_res = cr.fetchone()
1719 view_id = view_ref_res[0]
1722 cr.execute("""SELECT arch,name,field_parent,id,type,inherit_id,model
1724 WHERE id=%s""", (view_id,))
1726 cr.execute('''SELECT
1727 arch,name,field_parent,id,type,inherit_id,model
1734 ORDER BY priority''', (self._name, view_type))
1735 sql_res = cr.fetchone()
1741 view_id = ok or sql_res[3]
1743 parent_view_model = sql_res[6]
1745 # if a view was found
1747 result['type'] = sql_res[4]
1748 result['view_id'] = sql_res[3]
1749 result['arch'] = sql_res[0]
1751 def _inherit_apply_rec(result, inherit_id):
1752 # get all views which inherit from (ie modify) this view
1753 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1754 sql_inherit = cr.fetchall()
1755 for (inherit, id) in sql_inherit:
1756 result = _inherit_apply(result, inherit, id)
1757 result = _inherit_apply_rec(result, id)
1760 inherit_result = etree.fromstring(encode(result['arch']))
1761 result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1763 result['name'] = sql_res[1]
1764 result['field_parent'] = sql_res[2] or False
1767 # otherwise, build some kind of default view
1768 if view_type == 'form':
1769 res = self.fields_get(cr, user, context=context)
1770 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1771 '<form string="%s">' % (self._description,)
1773 if res[x]['type'] not in ('one2many', 'many2many'):
1774 xml += '<field name="%s"/>' % (x,)
1775 if res[x]['type'] == 'text':
1779 elif view_type == 'tree':
1780 _rec_name = self._rec_name
1781 if _rec_name not in self._columns:
1782 _rec_name = self._columns.keys()[0]
1783 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1784 '<tree string="%s"><field name="%s"/></tree>' \
1785 % (self._description, _rec_name)
1787 elif view_type == 'calendar':
1788 xml = self.__get_default_calendar_view()
1790 elif view_type == 'search':
1791 xml = self.__get_default_search_view(cr, user, context)
1794 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1795 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1796 result['arch'] = etree.fromstring(encode(xml))
1797 result['name'] = 'default'
1798 result['field_parent'] = False
1799 result['view_id'] = 0
1801 if parent_view_model != self._name:
1802 ctx = context.copy()
1803 ctx['base_model_name'] = parent_view_model
1806 xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1807 result['arch'] = xarch
1808 result['fields'] = xfields
1811 if context and context.get('active_id', False):
1812 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1814 act_id = data_menu.id
1816 data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1817 result['submenu'] = getattr(data_action, 'menus', False)
1821 for key in ('report_sxw_content', 'report_rml_content',
1822 'report_sxw', 'report_rml',
1823 'report_sxw_content_data', 'report_rml_content_data'):
1827 ir_values_obj = self.pool.get('ir.values')
1828 resprint = ir_values_obj.get(cr, user, 'action',
1829 'client_print_multi', [(self._name, False)], False,
1831 resaction = ir_values_obj.get(cr, user, 'action',
1832 'client_action_multi', [(self._name, False)], False,
1835 resrelate = ir_values_obj.get(cr, user, 'action',
1836 'client_action_relate', [(self._name, False)], False,
1838 resprint = map(clean, resprint)
1839 resaction = map(clean, resaction)
1840 resaction = filter(lambda x: not x.get('multi', False), resaction)
1841 resprint = filter(lambda x: not x.get('multi', False), resprint)
1842 resrelate = map(lambda x: x[2], resrelate)
1844 for x in resprint + resaction + resrelate:
1845 x['string'] = x['name']
1847 result['toolbar'] = {
1849 'action': resaction,
1854 _view_look_dom_arch = __view_look_dom_arch
1856 def search_count(self, cr, user, args, context=None):
1859 res = self.search(cr, user, args, context=context, count=True)
1860 if isinstance(res, list):
1864 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1866 Search for records based on a search domain.
1868 :param cr: database cursor
1869 :param user: current user id
1870 :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1871 :param offset: optional number of results to skip in the returned values (default: 0)
1872 :param limit: optional max number of records to return (default: **None**)
1873 :param order: optional columns to sort by (default: self._order=id )
1874 :param context: optional context arguments, like lang, time zone
1875 :type context: dictionary
1876 :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1877 :return: id or list of ids of records matching the criteria
1878 :rtype: integer or list of integers
1879 :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1881 **Expressing a search domain (args)**
1883 Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1885 * **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.
1886 * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1887 The semantics of most of these operators are obvious.
1888 The ``child_of`` operator will look for records who are children or grand-children of a given record,
1889 according to the semantics of this model (i.e following the relationship field named by
1890 ``self._parent_name``, by default ``parent_id``.
1891 * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1893 Domain criteria can be combined using 3 logical operators than can be added between tuples: '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1894 These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1895 Be very careful about this when you combine them the first time.
1897 Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1899 [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1901 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::
1903 (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1906 return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1908 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1910 Private implementation of search() method, allowing specifying the uid to use for the access right check.
1911 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1912 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1914 :param access_rights_uid: optional user ID to use when checking access rights
1915 (not for ir.rules, this is only for ir.model.access)
1917 raise NotImplementedError(_('The search method is not implemented on this object !'))
1919 def name_get(self, cr, user, ids, context=None):
1922 :param cr: database cursor
1923 :param user: current user id
1925 :param ids: list of ids
1926 :param context: context arguments, like lang, time zone
1927 :type context: dictionary
1928 :return: tuples with the text representation of requested objects for to-many relationships
1935 if isinstance(ids, (int, long)):
1937 return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1938 [self._rec_name], context, load='_classic_write')]
1940 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1942 Search for records and their display names according to a search domain.
1944 :param cr: database cursor
1945 :param user: current user id
1946 :param name: object name to search
1947 :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1948 :param operator: operator for search criterion
1949 :param context: context arguments, like lang, time zone
1950 :type context: dictionary
1951 :param limit: optional max number of records to return
1952 :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1954 This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1955 See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1958 return self._name_search(cr, user, name, args, operator, context, limit)
1960 # private implementation of name_search, allows passing a dedicated user for the name_get part to
1961 # solve some access rights issues
1962 def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1969 args += [(self._rec_name, operator, name)]
1970 access_rights_uid = name_get_uid or user
1971 ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1972 res = self.name_get(cr, access_rights_uid, ids, context)
1975 def copy(self, cr, uid, id, default=None, context=None):
1976 raise NotImplementedError(_('The copy method is not implemented on this object !'))
1978 def exists(self, cr, uid, id, context=None):
1979 raise NotImplementedError(_('The exists method is not implemented on this object !'))
1981 def read_string(self, cr, uid, id, langs, fields=None, context=None):
1984 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1986 fields = self._columns.keys() + self._inherit_fields.keys()
1987 #FIXME: collect all calls to _get_source into one SQL call.
1989 res[lang] = {'code': lang}
1991 if f in self._columns:
1992 res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1994 res[lang][f] = res_trans
1996 res[lang][f] = self._columns[f].string
1997 for table in self._inherits:
1998 cols = intersect(self._inherit_fields.keys(), fields)
1999 res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
2002 res[lang]['code'] = lang
2003 for f in res2[lang]:
2004 res[lang][f] = res2[lang][f]
2007 def write_string(self, cr, uid, id, langs, vals, context=None):
2008 self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
2009 #FIXME: try to only call the translation in one SQL
2012 if field in self._columns:
2013 src = self._columns[field].string
2014 self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
2015 for table in self._inherits:
2016 cols = intersect(self._inherit_fields.keys(), vals)
2018 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2021 def _check_removed_columns(self, cr, log=False):
2022 raise NotImplementedError()
2024 def _add_missing_default_values(self, cr, uid, values, context=None):
2025 missing_defaults = []
2026 avoid_tables = [] # avoid overriding inherited values when parent is set
2027 for tables, parent_field in self._inherits.items():
2028 if parent_field in values:
2029 avoid_tables.append(tables)
2030 for field in self._columns.keys():
2031 if not field in values:
2032 missing_defaults.append(field)
2033 for field in self._inherit_fields.keys():
2034 if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
2035 missing_defaults.append(field)
2037 if len(missing_defaults):
2038 # override defaults with the provided values, never allow the other way around
2039 defaults = self.default_get(cr, uid, missing_defaults, context)
2041 if ((dv in self._columns and self._columns[dv]._type == 'many2many') \
2042 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many')) \
2043 and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
2044 defaults[dv] = [(6, 0, defaults[dv])]
2045 if (dv in self._columns and self._columns[dv]._type == 'one2many' \
2046 or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many')) \
2047 and isinstance(defaults[dv], (list, tuple)) and defaults[dv] and isinstance(defaults[dv][0], dict):
2048 defaults[dv] = [(0, 0, x) for x in defaults[dv]]
2049 defaults.update(values)
2053 class orm_memory(orm_template):
2055 _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']
2056 _inherit_fields = {}
2062 def createInstance(cls, pool, cr):
2063 return cls.makeInstance(pool, cr, ['_columns', '_defaults'])
2065 def __init__(self, pool, cr):
2066 super(orm_memory, self).__init__(pool, cr)
2070 self._max_count = config.get('osv_memory_count_limit')
2071 self._max_hours = config.get('osv_memory_age_limit')
2072 cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2074 def _check_access(self, uid, object_id, mode):
2075 if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2076 raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2078 def vaccum(self, cr, uid, force=False):
2079 """Run the vaccuum cleaning system, expiring and removing old records from the
2080 virtual osv_memory tables if the "max count" or "max age" conditions are enabled
2081 and have been reached. This method can be called very often (e.g. everytime a record
2082 is created), but will only actually trigger the cleanup process once out of
2083 "_check_time" times (by default once out of 20 calls)."""
2085 if (not force) and (self.check_id % self._check_time):
2089 # Age-based expiration
2091 max = time.time() - self._max_hours * 60 * 60
2092 for k,v in self.datas.iteritems():
2093 if v['internal.date_access'] < max:
2095 self.unlink(cr, 1, tounlink)
2097 # Count-based expiration
2098 if self._max_count and len(self.datas) > self._max_count:
2099 # sort by access time to remove only the first/oldest ones in LRU fashion
2100 records = self.datas.items()
2101 records.sort(key=lambda x:x[1]['internal.date_access'])
2102 self.unlink(cr, 1, [x[0] for x in records[:len(self.datas)-self._max_count]])
2106 def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2109 if not fields_to_read:
2110 fields_to_read = self._columns.keys()
2114 if isinstance(ids, (int, long)):
2118 for f in fields_to_read:
2119 record = self.datas.get(id)
2121 self._check_access(user, id, 'read')
2122 r[f] = record.get(f, False)
2123 if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2126 if id in self.datas:
2127 self.datas[id]['internal.date_access'] = time.time()
2128 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2129 for f in fields_post:
2130 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2131 for record in result:
2132 record[f] = res2[record['id']]
2133 if isinstance(ids_orig, (int, long)):
2137 def write(self, cr, user, ids, vals, context=None):
2143 if self._columns[field]._classic_write:
2144 vals2[field] = vals[field]
2146 upd_todo.append(field)
2147 for object_id in ids:
2148 self._check_access(user, object_id, mode='write')
2149 self.datas[object_id].update(vals2)
2150 self.datas[object_id]['internal.date_access'] = time.time()
2151 for field in upd_todo:
2152 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2153 self._validate(cr, user, [object_id], context)
2154 wf_service = netsvc.LocalService("workflow")
2155 wf_service.trg_write(user, self._name, object_id, cr)
2158 def create(self, cr, user, vals, context=None):
2159 self.vaccum(cr, user)
2161 id_new = self.next_id
2163 vals = self._add_missing_default_values(cr, user, vals, context)
2168 if self._columns[field]._classic_write:
2169 vals2[field] = vals[field]
2171 upd_todo.append(field)
2172 self.datas[id_new] = vals2
2173 self.datas[id_new]['internal.date_access'] = time.time()
2174 self.datas[id_new]['internal.create_uid'] = user
2176 for field in upd_todo:
2177 self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2178 self._validate(cr, user, [id_new], context)
2179 if self._log_create and not (context and context.get('no_store_function', False)):
2180 message = self._description + \
2182 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2184 self.log(cr, user, id_new, message, True, context=context)
2185 wf_service = netsvc.LocalService("workflow")
2186 wf_service.trg_create(user, self._name, id_new, cr)
2189 def _where_calc(self, cr, user, args, active_test=True, context=None):
2194 # if the object has a field named 'active', filter out all inactive
2195 # records unless they were explicitely asked for
2196 if 'active' in self._columns and (active_test and context.get('active_test', True)):
2198 active_in_args = False
2200 if a[0] == 'active':
2201 active_in_args = True
2202 if not active_in_args:
2203 args.insert(0, ('active', '=', 1))
2205 args = [('active', '=', 1)]
2208 e = expression.expression(args)
2209 e.parse(cr, user, self, context)
2213 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2217 # implicit filter on current user except for superuser
2221 args.insert(0, ('internal.create_uid', '=', user))
2223 result = self._where_calc(cr, user, args, context=context)
2225 return self.datas.keys()
2229 #Find the value of dict
2232 for id, data in self.datas.items():
2233 counter = counter + 1
2235 if limit and (counter > int(limit)):
2240 val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2241 elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2242 val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2243 elif arg[1] in ['ilike']:
2244 val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2254 def unlink(self, cr, uid, ids, context=None):
2256 self._check_access(uid, id, 'unlink')
2257 self.datas.pop(id, None)
2259 cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2262 def perm_read(self, cr, user, ids, context=None, details=True):
2264 credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2265 create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2267 self._check_access(user, id, 'read')
2269 'create_uid': credentials,
2270 'create_date': create_date,
2272 'write_date': False,
2278 def _check_removed_columns(self, cr, log=False):
2279 # nothing to check in memory...
2282 def exists(self, cr, uid, id, context=None):
2283 return id in self.datas
2285 class orm(orm_template):
2286 _sql_constraints = []
2288 _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']
2289 __logger = logging.getLogger('orm')
2290 __schema = logging.getLogger('orm.schema')
2291 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2293 Get the list of records in list view grouped by the given ``groupby`` fields
2295 :param cr: database cursor
2296 :param uid: current user id
2297 :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2298 :param list fields: list of fields present in the list view specified on the object
2299 :param list groupby: fields by which the records will be grouped
2300 :param int offset: optional number of records to skip
2301 :param int limit: optional max number of records to return
2302 :param dict context: context arguments, like lang, time zone
2303 :param order: optional ``order by`` specification, for overriding the natural
2304 sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2305 (supported only for many2one fields currently)
2306 :return: list of dictionaries(one dictionary for each record) containing:
2308 * the values of fields grouped by the fields in ``groupby`` argument
2309 * __domain: list of tuples specifying the search criteria
2310 * __context: dictionary with argument like ``groupby``
2311 :rtype: [{'field_name_1': value, ...]
2312 :raise AccessError: * if user has no read rights on the requested object
2313 * if user tries to bypass access rules for read on the requested object
2316 context = context or {}
2317 self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2319 fields = self._columns.keys()
2321 query = self._where_calc(cr, uid, domain, context=context)
2322 self._apply_ir_rules(cr, uid, query, 'read', context=context)
2324 # Take care of adding join(s) if groupby is an '_inherits'ed field
2325 groupby_list = groupby
2326 qualified_groupby_field = groupby
2328 if isinstance(groupby, list):
2329 groupby = groupby[0]
2330 qualified_groupby_field = self._inherits_join_calc(groupby, query)
2333 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?)"
2334 groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2335 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"
2337 fget = self.fields_get(cr, uid, fields)
2338 float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2340 group_count = group_by = groupby
2342 if fget.get(groupby):
2343 if fget[groupby]['type'] in ('date', 'datetime'):
2344 flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
2345 groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
2346 qualified_groupby_field = groupby
2348 flist = qualified_groupby_field
2350 # Don't allow arbitrary values, as this would be a SQL injection vector!
2351 raise except_orm(_('Invalid group_by'),
2352 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2355 fields_pre = [f for f in float_int_fields if
2356 f == self.CONCURRENCY_CHECK_FIELD
2357 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2358 for f in fields_pre:
2359 if f not in ['id', 'sequence']:
2360 group_operator = fget[f].get('group_operator', 'sum')
2363 qualified_field = '"%s"."%s"' % (self._table, f)
2364 flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2366 gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2368 from_clause, where_clause, where_clause_params = query.get_sql()
2369 where_clause = where_clause and ' WHERE ' + where_clause
2370 limit_str = limit and ' limit %d' % limit or ''
2371 offset_str = offset and ' offset %d' % offset or ''
2372 if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2374 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)
2377 for r in cr.dictfetchall():
2378 for fld, val in r.items():
2379 if val == None: r[fld] = False
2380 alldata[r['id']] = r
2383 data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2384 # the IDS of records that have groupby field value = False or '' should be sorted too
2385 data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2386 data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2387 # 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):
2388 data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2392 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2393 if not isinstance(groupby_list, (str, unicode)):
2394 if groupby or not context.get('group_by_no_leaf', False):
2395 d['__context'] = {'group_by': groupby_list[1:]}
2396 if groupby and groupby in fget:
2397 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2398 dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2399 days = calendar.monthrange(dt.year, dt.month)[1]
2401 d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2402 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),\
2403 (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
2404 del alldata[d['id']][groupby]
2405 d.update(alldata[d['id']])
2409 def _inherits_join_add(self, parent_model_name, query):
2411 Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2413 :param parent_model_name: name of the parent model for which the clauses should be added
2414 :param query: query object on which the JOIN should be added
2416 inherits_field = self._inherits[parent_model_name]
2417 parent_model = self.pool.get(parent_model_name)
2418 parent_table_name = parent_model._table
2419 quoted_parent_table_name = '"%s"' % parent_table_name
2420 if quoted_parent_table_name not in query.tables:
2421 query.tables.append(quoted_parent_table_name)
2422 query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2424 def _inherits_join_calc(self, field, query):
2426 Adds missing table select and join clause(s) to ``query`` for reaching
2427 the field coming from an '_inherits' parent table (no duplicates).
2429 :param field: name of inherited field to reach
2430 :param query: query object on which the JOIN should be added
2431 :return: qualified name of field, to be used in SELECT clause
2433 current_table = self
2434 while field in current_table._inherit_fields and not field in current_table._columns:
2435 parent_model_name = current_table._inherit_fields[field][0]
2436 parent_table = self.pool.get(parent_model_name)
2437 self._inherits_join_add(parent_model_name, query)
2438 current_table = parent_table
2439 return '"%s".%s' % (current_table._table, field)
2441 def _parent_store_compute(self, cr):
2442 if not self._parent_store:
2444 logger = netsvc.Logger()
2445 logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2446 def browse_rec(root, pos=0):
2448 where = self._parent_name+'='+str(root)
2450 where = self._parent_name+' IS NULL'
2451 if self._parent_order:
2452 where += ' order by '+self._parent_order
2453 cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2455 for id in cr.fetchall():
2456 pos2 = browse_rec(id[0], pos2)
2457 cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2459 query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2460 if self._parent_order:
2461 query += ' order by ' + self._parent_order
2464 for (root,) in cr.fetchall():
2465 pos = browse_rec(root, pos)
2468 def _update_store(self, cr, f, k):
2469 logger = netsvc.Logger()
2470 logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2471 ss = self._columns[k]._symbol_set
2472 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2473 cr.execute('select id from '+self._table)
2474 ids_lst = map(lambda x: x[0], cr.fetchall())
2477 ids_lst = ids_lst[40:]
2478 res = f.get(cr, self, iids, k, 1, {})
2479 for key, val in res.items():
2482 # if val is a many2one, just write the ID
2483 if type(val) == tuple:
2485 if (val<>False) or (type(val)<>bool):
2486 cr.execute(update_query, (ss[1](val), key))
2488 def _check_selection_field_value(self, cr, uid, field, value, context=None):
2489 """Raise except_orm if value is not among the valid values for the selection field"""
2490 if self._columns[field]._type == 'reference':
2491 val_model, val_id_str = value.split(',', 1)
2494 val_id = long(val_id_str)
2498 raise except_orm(_('ValidateError'),
2499 _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2503 if isinstance(self._columns[field].selection, (tuple, list)):
2504 if val in dict(self._columns[field].selection):
2506 elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2508 raise except_orm(_('ValidateError'),
2509 _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2511 def _check_removed_columns(self, cr, log=False):
2512 # iterate on the database columns to drop the NOT NULL constraints
2513 # of fields which were required but have been removed (or will be added by another module)
2514 columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2515 columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2516 cr.execute("SELECT a.attname, a.attnotnull"
2517 " FROM pg_class c, pg_attribute a"
2518 " WHERE c.relname=%s"
2519 " AND c.oid=a.attrelid"
2520 " AND a.attisdropped=%s"
2521 " AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2522 " AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2524 for column in cr.dictfetchall():
2526 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2527 column['attname'], self._table, self._name)
2528 if column['attnotnull']:
2529 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2530 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2531 self._table, column['attname'])
2533 def _auto_init(self, cr, context=None):
2536 Call _field_create and, unless _auto is False:
2538 - create the corresponding table in database for the model,
2539 - possibly add the parent columns in database,
2540 - possibly add the columns 'create_uid', 'create_date', 'write_uid',
2541 'write_date' in database if _log_access is True (the default),
2542 - report on database columns no more existing in _columns,
2543 - remove no more existing not null constraints,
2544 - alter existing database columns to match _columns,
2545 - create database tables to match _columns,
2546 - add database indices to match _columns,
2549 raise_on_invalid_object_name(self._name)
2552 store_compute = False
2555 self._field_create(cr, context=context)
2556 if getattr(self, '_auto', True):
2557 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2559 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2560 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2562 self.__schema.debug("Table '%s': created", self._table)
2565 if self._parent_store:
2566 cr.execute("""SELECT c.relname
2567 FROM pg_class c, pg_attribute a
2568 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2569 """, (self._table, 'parent_left'))
2571 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2572 cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2573 if 'parent_left' not in self._columns:
2574 self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2576 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2577 self._table, 'parent_left', 'INTEGER')
2578 elif not self._columns['parent_left'].select:
2579 self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2581 if 'parent_right' not in self._columns:
2582 self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2584 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2585 self._table, 'parent_right', 'INTEGER')
2586 elif not self._columns['parent_right'].select:
2587 self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2589 if self._columns[self._parent_name].ondelete != 'cascade':
2590 self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2591 self._parent_name, self._name)
2594 store_compute = True
2596 if self._log_access:
2598 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2599 'create_date': 'TIMESTAMP',
2600 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2601 'write_date': 'TIMESTAMP'
2606 FROM pg_class c, pg_attribute a
2607 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2608 """, (self._table, k))
2610 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2612 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2613 self._table, k, logs[k])
2615 self._check_removed_columns(cr, log=False)
2617 # iterate on the "object columns"
2618 todo_update_store = []
2619 update_custom_fields = context.get('update_custom_fields', False)
2621 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 " \
2622 "FROM pg_class c,pg_attribute a,pg_type t " \
2623 "WHERE c.relname=%s " \
2624 "AND c.oid=a.attrelid " \
2625 "AND a.atttypid=t.oid", (self._table,))
2626 col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2629 for k in self._columns:
2630 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2632 #Not Updating Custom fields
2633 if k.startswith('x_') and not update_custom_fields:
2636 f = self._columns[k]
2638 if isinstance(f, fields.one2many):
2639 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2641 if self.pool.get(f._obj):
2642 if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2643 if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2644 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2647 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))
2648 res = cr.fetchone()[0]
2650 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2651 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2652 self._obj, f._fields_id, f._table)
2653 elif isinstance(f, fields.many2many):
2654 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2655 if not cr.dictfetchall():
2656 if not self.pool.get(f._obj):
2657 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2658 ref = self.pool.get(f._obj)._table
2659 # ref = f._obj.replace('.', '_')
2660 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))
2661 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2662 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2663 cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2665 self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2666 f._rel, self._table, ref)
2668 res = col_data.get(k, [])
2669 res = res and [res] or []
2670 if not res and hasattr(f, 'oldname'):
2671 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 " \
2672 "FROM pg_class c,pg_attribute a,pg_type t " \
2673 "WHERE c.relname=%s " \
2674 "AND a.attname=%s " \
2675 "AND c.oid=a.attrelid " \
2676 "AND a.atttypid=t.oid", (self._table, f.oldname))
2677 res_old = cr.dictfetchall()
2678 if res_old and len(res_old) == 1:
2679 cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2681 res[0]['attname'] = k
2682 self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2683 self._table, f.oldname, k)
2687 f_pg_type = f_pg_def['typname']
2688 f_pg_size = f_pg_def['size']
2689 f_pg_notnull = f_pg_def['attnotnull']
2690 if isinstance(f, fields.function) and not f.store and\
2691 not getattr(f, 'nodrop', False):
2692 self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2693 k, f.string, self._table)
2694 cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2696 self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2700 f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2705 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2706 ('varchar', 'text', 'TEXT', ''),
2707 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2708 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2709 ('timestamp', 'date', 'date', '::date'),
2710 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2711 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2713 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2714 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2715 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2716 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2717 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2719 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2720 self._table, k, f_pg_size, f.size)
2722 if (f_pg_type==c[0]) and (f._type==c[1]):
2723 if f_pg_type != f_obj_type:
2725 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2726 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2727 cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2728 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2730 self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2731 self._table, k, c[0], c[1])
2734 if f_pg_type != f_obj_type:
2738 newname = k + '_moved' + str(i)
2739 cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2740 "WHERE c.relname=%s " \
2741 "AND a.attname=%s " \
2742 "AND c.oid=a.attrelid ", (self._table, newname))
2743 if not cr.fetchone()[0]:
2747 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2748 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2749 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2750 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2751 self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2752 self._table, k, f_pg_type, f._type, newname)
2754 # if the field is required and hasn't got a NOT NULL constraint
2755 if f.required and f_pg_notnull == 0:
2756 # set the field to the default value if any
2757 if k in self._defaults:
2758 if callable(self._defaults[k]):
2759 default = self._defaults[k](self, cr, 1, context)
2761 default = self._defaults[k]
2763 if (default is not None):
2764 ss = self._columns[k]._symbol_set
2765 query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2766 cr.execute(query, (ss[1](default),))
2767 # add the NOT NULL constraint
2770 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2772 self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2775 msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2776 "If you want to have it, you should update the records and execute manually:\n"\
2777 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2778 self.__schema.warn(msg, self._table, k, self._table, k)
2780 elif not f.required and f_pg_notnull == 1:
2781 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2783 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2786 indexname = '%s_%s_index' % (self._table, k)
2787 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2788 res2 = cr.dictfetchall()
2789 if not res2 and f.select:
2790 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2792 if f._type == 'text':
2793 # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2794 msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2795 "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2796 " because there is a length limit for indexable btree values!\n"\
2797 "Use a search view instead if you simply want to make the field searchable."
2798 self.__schema.warn(msg, self._table, k, f._type)
2799 if res2 and not f.select:
2800 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2802 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2803 self.__schema.debug(msg, self._table, k, f._type)
2805 if isinstance(f, fields.many2one):
2806 ref = self.pool.get(f._obj)._table
2807 if ref != 'ir_actions':
2808 cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2809 'pg_attribute as att1, pg_attribute as att2 '
2810 'WHERE con.conrelid = cl1.oid '
2811 'AND cl1.relname = %s '
2812 'AND con.confrelid = cl2.oid '
2813 'AND cl2.relname = %s '
2814 'AND array_lower(con.conkey, 1) = 1 '
2815 'AND con.conkey[1] = att1.attnum '
2816 'AND att1.attrelid = cl1.oid '
2817 'AND att1.attname = %s '
2818 'AND array_lower(con.confkey, 1) = 1 '
2819 'AND con.confkey[1] = att2.attnum '
2820 'AND att2.attrelid = cl2.oid '
2821 'AND att2.attname = %s '
2822 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2823 res2 = cr.dictfetchall()
2825 if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2826 cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2827 cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2829 self.__schema.debug("Table '%s': column '%s': XXX",
2832 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2834 if not isinstance(f, fields.function) or f.store:
2835 # add the missing field
2836 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2837 cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2838 self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2839 self._table, k, get_pg_type(f)[1])
2842 if not create and k in self._defaults:
2843 if callable(self._defaults[k]):
2844 default = self._defaults[k](self, cr, 1, context)
2846 default = self._defaults[k]
2848 ss = self._columns[k]._symbol_set
2849 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2850 cr.execute(query, (ss[1](default),))
2852 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2854 if isinstance(f, fields.function):
2856 if f.store is not True:
2857 order = f.store[f.store.keys()[0]][2]
2858 todo_update_store.append((order, f, k))
2860 # and add constraints if needed
2861 if isinstance(f, fields.many2one):
2862 if not self.pool.get(f._obj):
2863 raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2864 ref = self.pool.get(f._obj)._table
2865 # ref = f._obj.replace('.', '_')
2866 # ir_actions is inherited so foreign key doesn't work on it
2867 if ref != 'ir_actions':
2868 cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2869 self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2870 self._table, k, ref, f.ondelete)
2872 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2876 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2877 self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2880 msg = "WARNING: unable to set column %s of table %s not null !\n"\
2881 "Try to re-run: openerp-server --update=module\n"\
2882 "If it doesn't work, update records and execute manually:\n"\
2883 "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2884 self.__logger.warn(msg, k, self._table, self._table, k)
2886 for order, f, k in todo_update_store:
2887 todo_end.append((order, self._update_store, (f, k)))
2890 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2891 create = not bool(cr.fetchone())
2893 cr.commit() # start a new transaction
2895 for (key, con, _) in self._sql_constraints:
2896 conname = '%s_%s' % (self._table, key)
2898 cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2899 existing_constraints = cr.dictfetchall()
2904 'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2905 'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2906 self._table, conname, con),
2907 'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2912 'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2913 'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2914 '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" % (
2920 if not existing_constraints:
2921 # constraint does not exists:
2922 sql_actions['add']['execute'] = True
2923 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2924 elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2925 # constraint exists but its definition has changed:
2926 sql_actions['drop']['execute'] = True
2927 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2928 sql_actions['add']['execute'] = True
2929 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2931 # we need to add the constraint:
2932 sql_actions = [item for item in sql_actions.values()]
2933 sql_actions.sort(key=lambda x: x['order'])
2934 for sql_action in [action for action in sql_actions if action['execute']]:
2936 cr.execute(sql_action['query'])
2938 self.__schema.debug(sql_action['msg_ok'])
2940 self.__schema.warn(sql_action['msg_err'])
2944 if hasattr(self, "_sql"):
2945 for line in self._sql.split(';'):
2946 line2 = line.replace('\n', '').strip()
2951 self._parent_store_compute(cr)
2956 def createInstance(cls, pool, cr):
2957 return cls.makeInstance(pool, cr, ['_columns', '_defaults',
2958 '_inherits', '_constraints', '_sql_constraints'])
2960 def __init__(self, pool, cr):
2963 - copy the stored fields' functions in the osv_pool,
2964 - update the _columns with the fields found in ir_model_fields,
2965 - ensure there is a many2one for each _inherits'd parent,
2966 - update the children's _columns,
2967 - give a chance to each field to initialize itself.
2970 super(orm, self).__init__(pool, cr)
2972 if not hasattr(self, '_log_access'):
2973 # if not access is not specify, it is the same value as _auto
2974 self._log_access = getattr(self, "_auto", True)
2976 self._columns = self._columns.copy()
2977 for store_field in self._columns:
2978 f = self._columns[store_field]
2979 if hasattr(f, 'digits_change'):
2981 if not isinstance(f, fields.function):
2985 if self._columns[store_field].store is True:
2986 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2988 sm = self._columns[store_field].store
2989 for object, aa in sm.items():
2991 (fnct, fields2, order, length) = aa
2993 (fnct, fields2, order) = aa
2996 raise except_orm('Error',
2997 ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2998 self.pool._store_function.setdefault(object, [])
3000 for x, y, z, e, f, l in self.pool._store_function[object]:
3001 if (x==self._name) and (y==store_field) and (e==fields2):
3005 self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
3006 self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
3008 for (key, _, msg) in self._sql_constraints:
3009 self.pool._sql_error[self._table+'_'+key] = msg
3011 # Load manual fields
3013 cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
3015 cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
3016 for field in cr.dictfetchall():
3017 if field['name'] in self._columns:
3020 'string': field['field_description'],
3021 'required': bool(field['required']),
3022 'readonly': bool(field['readonly']),
3023 'domain': eval(field['domain']) if field['domain'] else None,
3024 'size': field['size'],
3025 'ondelete': field['on_delete'],
3026 'translate': (field['translate']),
3028 #'select': int(field['select_level'])
3031 if field['ttype'] == 'selection':
3032 self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
3033 elif field['ttype'] == 'reference':
3034 self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
3035 elif field['ttype'] == 'many2one':
3036 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
3037 elif field['ttype'] == 'one2many':
3038 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
3039 elif field['ttype'] == 'many2many':
3040 _rel1 = field['relation'].replace('.', '_')
3041 _rel2 = field['model'].replace('.', '_')
3042 _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
3043 self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
3045 self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
3046 self._inherits_check()
3047 self._inherits_reload()
3048 if not self._sequence:
3049 self._sequence = self._table + '_id_seq'
3050 for k in self._defaults:
3051 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,)
3052 for f in self._columns:
3053 self._columns[f].restart()
3055 __init__.__doc__ = orm_template.__init__.__doc__ + __init__.__doc__
3058 # Update objects that uses this one to update their _inherits fields
3061 def _inherits_reload_src(self):
3062 for obj in self.pool.obj_pool.values():
3063 if self._name in obj._inherits:
3064 obj._inherits_reload()
3066 def _inherits_reload(self):
3068 for table in self._inherits:
3069 res.update(self.pool.get(table)._inherit_fields)
3070 for col in self.pool.get(table)._columns.keys():
3071 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
3072 for col in self.pool.get(table)._inherit_fields.keys():
3073 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
3074 self._inherit_fields = res
3075 self._inherits_reload_src()
3077 def _inherits_check(self):
3078 for table, field_name in self._inherits.items():
3079 if field_name not in self._columns:
3080 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
3081 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
3082 required=True, ondelete="cascade")
3083 elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
3084 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))
3085 self._columns[field_name].required = True
3086 self._columns[field_name].ondelete = "cascade"
3088 #def __getattr__(self, name):
3090 # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
3091 # (though inherits doesn't use Python inheritance).
3092 # Handles translating between local ids and remote ids.
3093 # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
3094 # when you have inherits.
3096 # for model, field in self._inherits.iteritems():
3097 # proxy = self.pool.get(model)
3098 # if hasattr(proxy, name):
3099 # attribute = getattr(proxy, name)
3100 # if not hasattr(attribute, '__call__'):
3104 # return super(orm, self).__getattr__(name)
3106 # def _proxy(cr, uid, ids, *args, **kwargs):
3107 # objects = self.browse(cr, uid, ids, kwargs.get('context', None))
3108 # lst = [obj[field].id for obj in objects if obj[field]]
3109 # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
3114 def fields_get(self, cr, user, fields=None, context=None):
3116 Get the description of list of fields
3118 :param cr: database cursor
3119 :param user: current user id
3120 :param fields: list of fields
3121 :param context: context arguments, like lang, time zone
3122 :return: dictionary of field dictionaries, each one describing a field of the business object
3123 :raise AccessError: * if user has no create/write rights on the requested object
3126 ira = self.pool.get('ir.model.access')
3127 write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
3128 ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
3129 return super(orm, self).fields_get(cr, user, fields, context, write_access)
3131 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
3134 self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3136 fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
3137 if isinstance(ids, (int, long)):
3141 select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
3142 result = self._read_flat(cr, user, select, fields, context, load)
3145 for key, v in r.items():
3149 if isinstance(ids, (int, long, dict)):
3150 return result and result[0] or False
3153 def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
3158 if fields_to_read == None:
3159 fields_to_read = self._columns.keys()
3161 # Construct a clause for the security rules.
3162 # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3163 # or will at least contain self._table.
3164 rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3166 # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
3167 fields_pre = [f for f in fields_to_read if
3168 f == self.CONCURRENCY_CHECK_FIELD
3169 or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3170 ] + self._inherits.values()
3174 def convert_field(f):
3175 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
3176 if f in ('create_date', 'write_date'):
3177 return "date_trunc('second', %s) as %s" % (f_qual, f)
3178 if f == self.CONCURRENCY_CHECK_FIELD:
3179 if self._log_access:
3180 return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
3181 return "now()::timestamp AS %s" % (f,)
3182 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
3183 return 'length(%s) as "%s"' % (f_qual, f)
3186 fields_pre2 = map(convert_field, fields_pre)
3187 order_by = self._parent_order or self._order
3188 select_fields = ','.join(fields_pre2 + [self._table + '.id'])
3189 query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3191 query += " AND " + (' OR '.join(rule_clause))
3192 query += " ORDER BY " + order_by
3193 for sub_ids in cr.split_for_in_conditions(ids):
3195 cr.execute(query, [tuple(sub_ids)] + rule_params)
3196 if cr.rowcount != len(sub_ids):
3197 raise except_orm(_('AccessError'),
3198 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3199 % (self._description,))
3201 cr.execute(query, (tuple(sub_ids),))
3202 res.extend(cr.dictfetchall())
3204 res = map(lambda x: {'id': x}, ids)
3206 for f in fields_pre:
3207 if f == self.CONCURRENCY_CHECK_FIELD:
3209 if self._columns[f].translate:
3210 ids = [x['id'] for x in res]
3211 #TODO: optimize out of this loop
3212 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3214 r[f] = res_trans.get(r['id'], False) or r[f]
3216 for table in self._inherits:
3217 col = self._inherits[table]
3218 cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
3221 res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3229 if not record[col]: # if the record is deleted from _inherits table?
3231 record.update(res3[record[col]])
3232 if col not in fields_to_read:
3235 # all fields which need to be post-processed by a simple function (symbol_get)
3236 fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3239 for f in fields_post:
3240 r[f] = self._columns[f]._symbol_get(r[f])
3241 ids = [x['id'] for x in res]
3243 # all non inherited fields for which the attribute whose name is in load is False
3244 fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3246 # Compute POST fields
3248 for f in fields_post:
3249 todo.setdefault(self._columns[f]._multi, [])
3250 todo[self._columns[f]._multi].append(f)
3251 for key, val in todo.items():
3253 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3254 assert res2 is not None, \
3255 'The function field "%s" on the "%s" model returned None\n' \
3256 '(a dictionary was expected).' % (val[0], self._name)
3259 if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3260 multi_fields = res2.get(record['id'],{})
3262 record[pos] = multi_fields.get(pos,[])
3265 res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3268 record[f] = res2[record['id']]
3273 for field in vals.copy():
3275 if field in self._columns:
3276 fobj = self._columns[field]
3283 for group in groups:
3284 module = group.split(".")[0]
3285 grp = group.split(".")[1]
3286 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", \
3287 (grp, module, 'res.groups', user))
3288 readonly = cr.fetchall()
3289 if readonly[0][0] >= 1:
3292 elif readonly[0][0] == 0:
3298 if type(vals[field]) == type([]):
3300 elif type(vals[field]) == type(0.0):
3302 elif type(vals[field]) == type(''):
3303 vals[field] = '=No Permission='
3308 def perm_read(self, cr, user, ids, context=None, details=True):
3310 Returns some metadata about the given records.
3312 :param details: if True, \*_uid fields are replaced with the name of the user
3313 :return: list of ownership dictionaries for each requested record
3314 :rtype: list of dictionaries with the following keys:
3317 * create_uid: user who created the record
3318 * create_date: date when the record was created
3319 * write_uid: last user who changed the record
3320 * write_date: date of the last change to the record
3321 * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3328 uniq = isinstance(ids, (int, long))
3332 if self._log_access:
3333 fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3334 quoted_table = '"%s"' % self._table
3335 fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3336 query = '''SELECT %s, __imd.module, __imd.name
3337 FROM %s LEFT JOIN ir_model_data __imd
3338 ON (__imd.model = %%s and __imd.res_id = %s.id)
3339 WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3340 cr.execute(query, (self._name, tuple(ids)))
3341 res = cr.dictfetchall()
3344 r[key] = r[key] or False
3345 if details and key in ('write_uid', 'create_uid') and r[key]:
3347 r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3349 pass # Leave the numeric uid there
3350 r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3351 del r['name'], r['module']
3356 def _check_concurrency(self, cr, ids, context):
3359 if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3361 check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3362 for sub_ids in cr.split_for_in_conditions(ids):
3365 id_ref = "%s,%s" % (self._name, id)
3366 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3368 ids_to_check.extend([id, update_date])
3369 if not ids_to_check:
3371 cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3374 # mention the first one only to keep the error message readable
3375 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3377 def check_access_rule(self, cr, uid, ids, operation, context=None):
3378 """Verifies that the operation given by ``operation`` is allowed for the user
3379 according to ir.rules.
3381 :param operation: one of ``write``, ``unlink``
3382 :raise except_orm: * if current ir.rules do not permit this operation.
3383 :return: None if the operation is allowed
3385 where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3387 where_clause = ' and ' + ' and '.join(where_clause)
3388 for sub_ids in cr.split_for_in_conditions(ids):
3389 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3390 ' WHERE ' + self._table + '.id IN %s' + where_clause,
3391 [sub_ids] + where_params)
3392 if cr.rowcount != len(sub_ids):
3393 raise except_orm(_('AccessError'),
3394 _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3395 % (operation, self._description))
3397 def unlink(self, cr, uid, ids, context=None):
3399 Delete records with given ids
3401 :param cr: database cursor
3402 :param uid: current user id
3403 :param ids: id or list of ids
3404 :param context: (optional) context arguments, like lang, time zone
3406 :raise AccessError: * if user has no unlink rights on the requested object
3407 * if user tries to bypass access rules for unlink on the requested object
3408 :raise UserError: if the record is default property for other records
3413 if isinstance(ids, (int, long)):
3416 result_store = self._store_get_values(cr, uid, ids, None, context)
3418 self._check_concurrency(cr, ids, context)
3420 self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3422 properties = self.pool.get('ir.property')
3423 domain = [('res_id', '=', False),
3424 ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3426 if properties.search(cr, uid, domain, context=context):
3427 raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3429 wf_service = netsvc.LocalService("workflow")
3431 wf_service.trg_delete(uid, self._name, oid, cr)
3434 self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3435 pool_model_data = self.pool.get('ir.model.data')
3436 ir_values_obj = self.pool.get('ir.values')
3437 for sub_ids in cr.split_for_in_conditions(ids):
3438 cr.execute('delete from ' + self._table + ' ' \
3439 'where id IN %s', (sub_ids,))
3441 # Removing the ir_model_data reference if the record being deleted is a record created by xml/csv file,
3442 # as these are not connected with real database foreign keys, and would be dangling references.
3443 # Step 1. Calling unlink of ir_model_data only for the affected IDS.
3444 referenced_ids = pool_model_data.search(cr, uid, [('res_id','in',list(sub_ids)),('model','=',self._name)], context=context)
3445 # Step 2. Marching towards the real deletion of referenced records
3446 pool_model_data.unlink(cr, uid, referenced_ids, context=context)
3448 # For the same reason, removing the record relevant to ir_values
3449 ir_value_ids = ir_values_obj.search(cr, uid,
3450 ['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
3453 ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
3455 for order, object, store_ids, fields in result_store:
3456 if object != self._name:
3457 obj = self.pool.get(object)
3458 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3459 rids = map(lambda x: x[0], cr.fetchall())
3461 obj._store_set_values(cr, uid, rids, fields, context)
3468 def write(self, cr, user, ids, vals, context=None):
3470 Update records with given ids with the given field values
3472 :param cr: database cursor
3473 :param user: current user id
3475 :param ids: object id or list of object ids to update according to **vals**
3476 :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3477 :type vals: dictionary
3478 :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3479 :type context: dictionary
3481 :raise AccessError: * if user has no write rights on the requested object
3482 * if user tries to bypass access rules for write on the requested object
3483 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3484 :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)
3486 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3488 + For a many2many field, a list of tuples is expected.
3489 Here is the list of tuple that are accepted, with the corresponding semantics ::
3491 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3492 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3493 (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)
3494 (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)
3495 (4, ID) link to existing record with id = ID (adds a relationship)
3496 (5) unlink all (like using (3,ID) for all linked records)
3497 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3500 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3502 + For a one2many field, a lits of tuples is expected.
3503 Here is the list of tuple that are accepted, with the corresponding semantics ::
3505 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
3506 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
3507 (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)
3510 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3512 + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3513 + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3517 for field in vals.copy():
3519 if field in self._columns:
3520 fobj = self._columns[field]
3521 elif field in self._inherit_fields:
3522 fobj = self._inherit_fields[field][2]
3529 for group in groups:
3530 module = group.split(".")[0]
3531 grp = group.split(".")[1]
3532 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", \
3533 (grp, module, 'res.groups', user))
3534 readonly = cr.fetchall()
3535 if readonly[0][0] >= 1:
3538 elif readonly[0][0] == 0:
3550 if isinstance(ids, (int, long)):
3553 self._check_concurrency(cr, ids, context)
3554 self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3556 result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3558 # No direct update of parent_left/right
3559 vals.pop('parent_left', None)
3560 vals.pop('parent_right', None)
3562 parents_changed = []
3563 parent_order = self._parent_order or self._order
3564 if self._parent_store and (self._parent_name in vals):
3565 # The parent_left/right computation may take up to
3566 # 5 seconds. No need to recompute the values if the
3567 # parent is the same.
3568 # Note: to respect parent_order, nodes must be processed in
3569 # order, so ``parents_changed`` must be ordered properly.
3570 parent_val = vals[self._parent_name]
3572 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL) ORDER BY %s" % \
3573 (self._table, self._parent_name, self._parent_name, parent_order)
3574 cr.execute(query, (tuple(ids), parent_val))
3576 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \
3577 (self._table, self._parent_name, parent_order)
3578 cr.execute(query, (tuple(ids),))
3579 parents_changed = map(operator.itemgetter(0), cr.fetchall())
3586 totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3588 if field in self._columns:
3589 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3590 if (not totranslate) or not self._columns[field].translate:
3591 upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3592 upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3593 direct.append(field)
3595 upd_todo.append(field)
3597 updend.append(field)
3598 if field in self._columns \
3599 and hasattr(self._columns[field], 'selection') \
3601 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3603 if self._log_access:
3604 upd0.append('write_uid=%s')
3605 upd0.append('write_date=now()')
3609 self.check_access_rule(cr, user, ids, 'write', context=context)
3610 for sub_ids in cr.split_for_in_conditions(ids):
3611 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3612 'where id IN %s', upd1 + [sub_ids])
3613 if cr.rowcount != len(sub_ids):
3614 raise except_orm(_('AccessError'),
3615 _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3620 if self._columns[f].translate:
3621 src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3624 # Inserting value to DB
3625 self.write(cr, user, ids, {f: vals[f]})
3626 self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3629 # call the 'set' method of fields which are not classic_write
3630 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3632 # default element in context must be removed when call a one2many or many2many
3633 rel_context = context.copy()
3634 for c in context.items():
3635 if c[0].startswith('default_'):
3636 del rel_context[c[0]]
3638 for field in upd_todo:
3640 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3642 for table in self._inherits:
3643 col = self._inherits[table]
3645 for sub_ids in cr.split_for_in_conditions(ids):
3646 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3647 'where id IN %s', (sub_ids,))
3648 nids.extend([x[0] for x in cr.fetchall()])
3652 if self._inherit_fields[val][0] == table:
3655 self.pool.get(table).write(cr, user, nids, v, context)
3657 self._validate(cr, user, ids, context)
3659 # TODO: use _order to set dest at the right position and not first node of parent
3660 # We can't defer parent_store computation because the stored function
3661 # fields that are computer may refer (directly or indirectly) to
3662 # parent_left/right (via a child_of domain)
3665 self.pool._init_parent[self._name] = True
3667 order = self._parent_order or self._order
3668 parent_val = vals[self._parent_name]
3670 clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3672 clause, params = '%s IS NULL' % (self._parent_name,), ()
3674 for id in parents_changed:
3675 cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3676 pleft, pright = cr.fetchone()
3677 distance = pright - pleft + 1
3679 # Positions of current siblings, to locate proper insertion point;
3680 # this can _not_ be fetched outside the loop, as it needs to be refreshed
3681 # after each update, in case several nodes are sequentially inserted one
3682 # next to the other (i.e computed incrementally)
3683 cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, parent_order), params)
3684 parents = cr.fetchall()
3686 # Find Position of the element
3688 for (parent_pright, parent_id) in parents:
3691 position = parent_pright + 1
3693 # It's the first node of the parent
3698 cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3699 position = cr.fetchone()[0] + 1
3701 if pleft < position <= pright:
3702 raise except_orm(_('UserError'), _('Recursivity Detected.'))
3704 if pleft < position:
3705 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3706 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3707 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))
3709 cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3710 cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3711 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))
3713 result += self._store_get_values(cr, user, ids, vals.keys(), context)
3717 for order, object, ids_to_update, fields_to_recompute in result:
3718 key = (object, tuple(fields_to_recompute))
3719 done.setdefault(key, {})
3720 # avoid to do several times the same computation
3722 for id in ids_to_update:
3723 if id not in done[key]:
3724 done[key][id] = True
3726 self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
3728 wf_service = netsvc.LocalService("workflow")
3730 wf_service.trg_write(user, self._name, id, cr)
3734 # TODO: Should set perm to user.xxx
3736 def create(self, cr, user, vals, context=None):
3738 Create a new record for the model.
3740 The values for the new record are initialized using the ``vals``
3741 argument, and if necessary the result of ``default_get()``.
3743 :param cr: database cursor
3744 :param user: current user id
3746 :param vals: field values for new record, e.g {'field_name': field_value, ...}
3747 :type vals: dictionary
3748 :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3749 :type context: dictionary
3750 :return: id of new record created
3751 :raise AccessError: * if user has no create rights on the requested object
3752 * if user tries to bypass access rules for create on the requested object
3753 :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3754 :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)
3756 **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3757 Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3763 self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3765 vals = self._add_missing_default_values(cr, user, vals, context)
3768 for v in self._inherits:
3769 if self._inherits[v] not in vals:
3772 tocreate[v] = {'id': vals[self._inherits[v]]}
3773 (upd0, upd1, upd2) = ('', '', [])
3775 for v in vals.keys():
3776 if v in self._inherit_fields:
3777 (table, col, col_detail) = self._inherit_fields[v]
3778 tocreate[table][v] = vals[v]
3781 if (v not in self._inherit_fields) and (v not in self._columns):
3784 # Try-except added to filter the creation of those records whose filds are readonly.
3785 # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3787 cr.execute("SELECT nextval('"+self._sequence+"')")
3789 raise except_orm(_('UserError'),
3790 _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3792 id_new = cr.fetchone()[0]
3793 for table in tocreate:
3794 if self._inherits[table] in vals:
3795 del vals[self._inherits[table]]
3797 record_id = tocreate[table].pop('id', None)
3799 if record_id is None or not record_id:
3800 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3802 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3804 upd0 += ',' + self._inherits[table]
3806 upd2.append(record_id)
3808 #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3809 bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3811 for bool_field in bool_fields:
3812 if bool_field not in vals:
3813 vals[bool_field] = False
3815 for field in vals.copy():
3817 if field in self._columns:
3818 fobj = self._columns[field]
3820 fobj = self._inherit_fields[field][2]
3826 for group in groups:
3827 module = group.split(".")[0]
3828 grp = group.split(".")[1]
3829 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" % \
3830 (grp, module, 'res.groups', user))
3831 readonly = cr.fetchall()
3832 if readonly[0][0] >= 1:
3835 elif readonly[0][0] == 0:
3843 if self._columns[field]._classic_write:
3844 upd0 = upd0 + ',"' + field + '"'
3845 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3846 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3848 if not isinstance(self._columns[field], fields.related):
3849 upd_todo.append(field)
3850 if field in self._columns \
3851 and hasattr(self._columns[field], 'selection') \
3853 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3854 if self._log_access:
3855 upd0 += ',create_uid,create_date'
3858 cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3859 self.check_access_rule(cr, user, [id_new], 'create', context=context)
3860 upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3862 if self._parent_store and not context.get('defer_parent_store_computation'):
3864 self.pool._init_parent[self._name] = True
3866 parent = vals.get(self._parent_name, False)
3868 cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3870 result_p = cr.fetchall()
3871 for (pleft,) in result_p:
3876 cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3877 pleft_old = cr.fetchone()[0]
3880 cr.execute('select max(parent_right) from '+self._table)
3881 pleft = cr.fetchone()[0] or 0
3882 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3883 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3884 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3886 # default element in context must be remove when call a one2many or many2many
3887 rel_context = context.copy()
3888 for c in context.items():
3889 if c[0].startswith('default_'):
3890 del rel_context[c[0]]
3893 for field in upd_todo:
3894 result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3895 self._validate(cr, user, [id_new], context)
3897 if not context.get('no_store_function', False):
3898 result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3901 for order, object, ids, fields2 in result:
3902 if not (object, ids, fields2) in done:
3903 self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3904 done.append((object, ids, fields2))
3906 if self._log_create and not (context and context.get('no_store_function', False)):
3907 message = self._description + \
3909 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3910 "' " + _("created.")
3911 self.log(cr, user, id_new, message, True, context=context)
3912 wf_service = netsvc.LocalService("workflow")
3913 wf_service.trg_create(user, self._name, id_new, cr)
3916 def _store_get_values(self, cr, uid, ids, fields, context):
3917 """Returns an ordered list of fields.functions to call due to
3918 an update operation on ``fields`` of records with ``ids``,
3919 obtained by calling the 'store' functions of these fields,
3920 as setup by their 'store' attribute.
3922 :return: [(priority, model_name, [record_ids,], [function_fields,])]
3924 # FIXME: rewrite, cleanup, use real variable names
3925 # e.g.: http://pastie.org/1222060
3927 fncts = self.pool._store_function.get(self._name, [])
3928 for fnct in range(len(fncts)):
3933 for f in (fields or []):
3934 if f in fncts[fnct][3]:
3940 result.setdefault(fncts[fnct][0], {})
3942 # uid == 1 for accessing objects having rules defined on store fields
3943 ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3944 for id in filter(None, ids2):
3945 result[fncts[fnct][0]].setdefault(id, [])
3946 result[fncts[fnct][0]][id].append(fnct)
3948 for object in result:
3950 for id, fnct in result[object].items():
3951 k2.setdefault(tuple(fnct), [])
3952 k2[tuple(fnct)].append(id)
3953 for fnct, id in k2.items():
3954 dict.setdefault(fncts[fnct[0]][4], [])
3955 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3963 def _store_set_values(self, cr, uid, ids, fields, context):
3964 """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
3965 respecting ``multi`` attributes), and stores the resulting values in the database directly."""
3970 if self._log_access:
3971 cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3975 field_dict.setdefault(r[0], [])
3976 res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3977 write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3978 for i in self.pool._store_function.get(self._name, []):
3980 up_write_date = write_date + datetime.timedelta(hours=i[5])
3981 if datetime.datetime.now() < up_write_date:
3983 field_dict[r[0]].append(i[1])
3989 if self._columns[f]._multi not in keys:
3990 keys.append(self._columns[f]._multi)
3991 todo.setdefault(self._columns[f]._multi, [])
3992 todo[self._columns[f]._multi].append(f)
3996 # uid == 1 for accessing objects having rules defined on store fields
3997 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3998 for id, value in result.items():
4000 for f in value.keys():
4001 if f in field_dict[id]:
4008 if self._columns[v]._type in ('many2one', 'one2one'):
4010 value[v] = value[v][0]
4013 upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
4014 upd1.append(self._columns[v]._symbol_set[1](value[v]))
4017 cr.execute('update "' + self._table + '" set ' + \
4018 ','.join(upd0) + ' where id = %s', upd1)
4022 # uid == 1 for accessing objects having rules defined on store fields
4023 result = self._columns[f].get(cr, self, ids, f, 1, context=context)
4024 for r in result.keys():
4026 if r in field_dict.keys():
4027 if f in field_dict[r]:
4029 for id, value in result.items():
4030 if self._columns[f]._type in ('many2one', 'one2one'):
4035 cr.execute('update "' + self._table + '" set ' + \
4036 '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
4042 def perm_write(self, cr, user, ids, fields, context=None):
4043 raise NotImplementedError(_('This method does not exist anymore'))
4045 # TODO: ameliorer avec NULL
4046 def _where_calc(self, cr, user, domain, active_test=True, context=None):
4047 """Computes the WHERE clause needed to implement an OpenERP domain.
4048 :param domain: the domain to compute
4050 :param active_test: whether the default filtering of records with ``active``
4051 field set to ``False`` should be applied.
4052 :return: the query expressing the given domain as provided in domain
4053 :rtype: osv.query.Query
4058 # if the object has a field named 'active', filter out all inactive
4059 # records unless they were explicitely asked for
4060 if 'active' in self._columns and (active_test and context.get('active_test', True)):
4062 active_in_args = False
4064 if a[0] == 'active':
4065 active_in_args = True
4066 if not active_in_args:
4067 domain.insert(0, ('active', '=', 1))
4069 domain = [('active', '=', 1)]
4073 e = expression.expression(domain)
4074 e.parse(cr, user, self, context)
4075 tables = e.get_tables()
4076 where_clause, where_params = e.to_sql()
4077 where_clause = where_clause and [where_clause] or []
4079 where_clause, where_params, tables = [], [], ['"%s"' % self._table]
4081 return Query(tables, where_clause, where_params)
4083 def _check_qorder(self, word):
4084 if not regex_order.match(word):
4085 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)'))
4088 def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
4089 """Add what's missing in ``query`` to implement all appropriate ir.rules
4090 (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
4092 :param query: the current query object
4094 def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
4096 if parent_model and child_object:
4097 # as inherited rules are being applied, we need to add the missing JOIN
4098 # to reach the parent table (if it was not JOINed yet in the query)
4099 child_object._inherits_join_add(parent_model, query)
4100 query.where_clause += added_clause
4101 query.where_clause_params += added_params
4102 for table in added_tables:
4103 if table not in query.tables:
4104 query.tables.append(table)
4108 # apply main rules on the object
4109 rule_obj = self.pool.get('ir.rule')
4110 apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
4112 # apply ir.rules from the parents (through _inherits)
4113 for inherited_model in self._inherits:
4114 kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
4115 apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
4117 def _generate_m2o_order_by(self, order_field, query):
4119 Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
4120 either native m2o fields or function/related fields that are stored, including
4121 intermediate JOINs for inheritance if required.
4123 :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
4125 if order_field not in self._columns and order_field in self._inherit_fields:
4126 # also add missing joins for reaching the table containing the m2o field
4127 qualified_field = self._inherits_join_calc(order_field, query)
4128 order_field_column = self._inherit_fields[order_field][2]
4130 qualified_field = '"%s"."%s"' % (self._table, order_field)
4131 order_field_column = self._columns[order_field]
4133 assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
4134 if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
4135 logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
4136 "to be used as ordering fields! Ignoring sorting for %s.%s",
4137 self._name, order_field)
4140 # figure out the applicable order_by for the m2o
4141 dest_model = self.pool.get(order_field_column._obj)
4142 m2o_order = dest_model._order
4143 if not regex_order.match(m2o_order):
4144 # _order is complex, can't use it here, so we default to _rec_name
4145 m2o_order = dest_model._rec_name
4147 # extract the field names, to be able to qualify them and add desc/asc
4149 for order_part in m2o_order.split(","):
4150 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
4151 m2o_order = m2o_order_list
4153 # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
4154 # as we don't want to exclude results that have NULL values for the m2o
4155 src_table, src_field = qualified_field.replace('"','').split('.', 1)
4156 query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
4157 qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
4158 return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
4161 def _generate_order_by(self, order_spec, query):
4163 Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
4164 a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
4166 :raise" except_orm in case order_spec is malformed
4168 order_by_clause = self._order
4170 order_by_elements = []
4171 self._check_qorder(order_spec)
4172 for order_part in order_spec.split(','):
4173 order_split = order_part.strip().split(' ')
4174 order_field = order_split[0].strip()
4175 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
4177 if order_field == 'id':
4178 order_by_clause = '"%s"."%s"' % (self._table, order_field)
4179 elif order_field in self._columns:
4180 order_column = self._columns[order_field]
4181 if order_column._classic_read:
4182 inner_clause = '"%s"."%s"' % (self._table, order_field)
4183 elif order_column._type == 'many2one':
4184 inner_clause = self._generate_m2o_order_by(order_field, query)
4186 continue # ignore non-readable or "non-joinable" fields
4187 elif order_field in self._inherit_fields:
4188 parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4189 order_column = parent_obj._columns[order_field]
4190 if order_column._classic_read:
4191 inner_clause = self._inherits_join_calc(order_field, query)
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
4197 if isinstance(inner_clause, list):
4198 for clause in inner_clause:
4199 order_by_elements.append("%s %s" % (clause, order_direction))
4201 order_by_elements.append("%s %s" % (inner_clause, order_direction))
4202 if order_by_elements:
4203 order_by_clause = ",".join(order_by_elements)
4205 return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4207 def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4209 Private implementation of search() method, allowing specifying the uid to use for the access right check.
4210 This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4211 by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4212 This is ok at the security level because this method is private and not callable through XML-RPC.
4214 :param access_rights_uid: optional user ID to use when checking access rights
4215 (not for ir.rules, this is only for ir.model.access)
4219 self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4221 query = self._where_calc(cr, user, args, context=context)
4222 self._apply_ir_rules(cr, user, query, 'read', context=context)
4223 order_by = self._generate_order_by(order, query)
4224 from_clause, where_clause, where_clause_params = query.get_sql()
4226 limit_str = limit and ' limit %d' % limit or ''
4227 offset_str = offset and ' offset %d' % offset or ''
4228 where_str = where_clause and (" WHERE %s" % where_clause) or ''
4231 cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4234 cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4236 return [x[0] for x in res]
4238 # returns the different values ever entered for one field
4239 # this is used, for example, in the client when the user hits enter on
4241 def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4244 if field in self._inherit_fields:
4245 return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4247 return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4249 def copy_data(self, cr, uid, id, default=None, context=None):
4251 Copy given record's data with all its fields values
4253 :param cr: database cursor
4254 :param user: current user id
4255 :param id: id of the record to copy
4256 :param default: field values to override in the original values of the copied record
4257 :type default: dictionary
4258 :param context: context arguments, like lang, time zone
4259 :type context: dictionary
4260 :return: dictionary containing all the field values
4266 # avoid recursion through already copied records in case of circular relationship
4267 seen_map = context.setdefault('__copy_data_seen',{})
4268 if id in seen_map.setdefault(self._name,[]):
4270 seen_map[self._name].append(id)
4274 if 'state' not in default:
4275 if 'state' in self._defaults:
4276 if callable(self._defaults['state']):
4277 default['state'] = self._defaults['state'](self, cr, uid, context)
4279 default['state'] = self._defaults['state']
4281 context_wo_lang = context.copy()
4282 if 'lang' in context:
4283 del context_wo_lang['lang']
4284 data = self.read(cr, uid, [id,], context=context_wo_lang)
4288 raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4290 fields = self.fields_get(cr, uid, context=context)
4292 ftype = fields[f]['type']
4294 if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4298 data[f] = default[f]
4299 elif 'function' in fields[f]:
4301 elif ftype == 'many2one':
4303 data[f] = data[f] and data[f][0]
4306 elif ftype in ('one2many', 'one2one'):
4308 rel = self.pool.get(fields[f]['relation'])
4310 # duplicate following the order of the ids
4311 # because we'll rely on it later for copying
4312 # translations in copy_translation()!
4314 for rel_id in data[f]:
4315 # the lines are first duplicated using the wrong (old)
4316 # parent but then are reassigned to the correct one thanks
4317 # to the (0, 0, ...)
4318 d = rel.copy_data(cr, uid, rel_id, context=context)
4320 res.append((0, 0, d))
4322 elif ftype == 'many2many':
4323 data[f] = [(6, 0, data[f])]
4327 # make sure we don't break the current parent_store structure and
4328 # force a clean recompute!
4329 for parent_column in ['parent_left', 'parent_right']:
4330 data.pop(parent_column, None)
4332 for v in self._inherits:
4333 del data[self._inherits[v]]
4336 def copy_translations(self, cr, uid, old_id, new_id, context=None):
4340 # avoid recursion through already copied records in case of circular relationship
4341 seen_map = context.setdefault('__copy_translations_seen',{})
4342 if old_id in seen_map.setdefault(self._name,[]):
4344 seen_map[self._name].append(old_id)
4346 trans_obj = self.pool.get('ir.translation')
4347 fields = self.fields_get(cr, uid, context=context)
4349 translation_records = []
4350 for field_name, field_def in fields.items():
4351 # we must recursively copy the translations for o2o and o2m
4352 if field_def['type'] in ('one2one', 'one2many'):
4353 target_obj = self.pool.get(field_def['relation'])
4354 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4355 # here we rely on the order of the ids to match the translations
4356 # as foreseen in copy_data()
4357 old_children = sorted(old_record[field_name])
4358 new_children = sorted(new_record[field_name])
4359 for (old_child, new_child) in zip(old_children, new_children):
4360 target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4361 # and for translatable fields we keep them for copy
4362 elif field_def.get('translate'):
4364 if field_name in self._columns:
4365 trans_name = self._name + "," + field_name
4366 elif field_name in self._inherit_fields:
4367 trans_name = self._inherit_fields[field_name][0] + "," + field_name
4369 trans_ids = trans_obj.search(cr, uid, [
4370 ('name', '=', trans_name),
4371 ('res_id', '=', old_id)
4373 translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4375 for record in translation_records:
4377 record['res_id'] = new_id
4378 trans_obj.create(cr, uid, record, context=context)
4381 def copy(self, cr, uid, id, default=None, context=None):
4383 Duplicate record with given id updating it with default values
4385 :param cr: database cursor
4386 :param uid: current user id
4387 :param id: id of the record to copy
4388 :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4389 :type default: dictionary
4390 :param context: context arguments, like lang, time zone
4391 :type context: dictionary
4397 context = context.copy()
4398 data = self.copy_data(cr, uid, id, default, context)
4399 new_id = self.create(cr, uid, data, context)
4400 self.copy_translations(cr, uid, id, new_id, context)
4403 def exists(self, cr, uid, ids, context=None):
4404 if type(ids) in (int, long):
4406 query = 'SELECT count(1) FROM "%s"' % (self._table)
4407 cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4408 return cr.fetchone()[0] == len(ids)
4410 def check_recursion(self, cr, uid, ids, context=None, parent=None):
4411 warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4412 self._name, DeprecationWarning, stacklevel=3)
4413 assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4414 "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4415 return self._check_recursion(cr, uid, ids, context, parent)
4417 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4419 Verifies that there is no loop in a hierarchical structure of records,
4420 by following the parent relationship using the **parent** field until a loop
4421 is detected or until a top-level record is found.
4423 :param cr: database cursor
4424 :param uid: current user id
4425 :param ids: list of ids of records to check
4426 :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4427 :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4431 parent = self._parent_name
4433 query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4436 for i in range(0, len(ids), cr.IN_MAX):
4437 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4438 cr.execute(query, (tuple(sub_ids_parent),))
4439 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4440 ids_parent = ids_parent2
4441 for i in ids_parent:
4446 def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
4447 """Find out the XML ID(s) of any database record.
4449 **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
4451 :return: map of ids to the list of their fully qualified XML IDs
4452 (empty list when there's none).
4454 model_data_obj = self.pool.get('ir.model.data')
4455 data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
4456 data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
4459 # can't use dict.fromkeys() as the list would be shared!
4461 for record in data_results:
4462 result[record['res_id']].append('%(module)s.%(name)s' % record)
4465 def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4466 """Find out the XML ID of any database record, if there
4467 is one. This method works as a possible implementation
4468 for a function field, to be able to add it to any
4469 model object easily, referencing it as ``osv.osv.get_xml_id``.
4471 When multiple XML IDs exist for a record, only one
4472 of them is returned (randomly).
4474 **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4476 :return: map of ids to their fully qualified XML ID,
4477 defaulting to an empty string when there's none
4478 (to be usable as a function field).
4480 results = self._get_xml_ids(cr, uid, ids)
4481 for k, v in results.items():
4488 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: