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 ##############################################################################
24 - relations (one2many, many2one, many2many)
28 * _classic_read: is a classic sql fields
40 from psycopg2 import Binary
43 import openerp.tools as tools
44 from openerp.tools.translate import _
45 from openerp.tools import float_round, float_repr
48 _logger = logging.getLogger(__name__)
50 def _symbol_set(symb):
51 if symb == None or symb == False:
53 elif isinstance(symb, unicode):
54 return symb.encode('utf-8')
58 class _column(object):
59 """ Base of all fields, a database column
61 An instance of this object is a *description* of a database column. It will
62 not hold any data, but only provide the methods to manipulate data of an
63 ORM record or even prepare/update the database to hold such a field of data.
73 _symbol_f = _symbol_set
74 _symbol_set = (_symbol_c, _symbol_f)
77 # used to hide a certain field type in the list of field types
80 def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
83 The 'manual' keyword argument specifies if the field is a custom one.
84 It corresponds to the 'state' column in ir_model_fields.
91 self.states = states or {}
93 self.readonly = readonly
94 self.required = required
96 self.help = args.get('help', '')
97 self.priority = priority
98 self.change_default = change_default
99 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
100 self.translate = translate
101 self._domain = domain
102 self._context = context
108 self.selectable = True
109 self.group_operator = args.get('group_operator', False)
112 setattr(self, a, args[a])
117 def set(self, cr, obj, id, name, value, user=None, context=None):
118 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
120 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
121 raise Exception(_('undefined get method !'))
123 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
124 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
125 res = obj.read(cr, uid, ids, [name], context=context)
126 return [x[name] for x in res]
129 # ---------------------------------------------------------
131 # ---------------------------------------------------------
132 class boolean(_column):
135 _symbol_f = lambda x: x and 'True' or 'False'
136 _symbol_set = (_symbol_c, _symbol_f)
138 def __init__(self, string='unknown', required=False, **args):
139 super(boolean, self).__init__(string=string, required=required, **args)
142 "required=True is deprecated: making a boolean field"
143 " `required` has no effect, as NULL values are "
144 "automatically turned into False.")
146 class integer(_column):
149 _symbol_f = lambda x: int(x or 0)
150 _symbol_set = (_symbol_c, _symbol_f)
151 _symbol_get = lambda self,x: x or 0
153 def __init__(self, string='unknown', required=False, **args):
154 super(integer, self).__init__(string=string, required=required, **args)
157 "required=True is deprecated: making an integer field"
158 " `required` has no effect, as NULL values are "
159 "automatically turned into 0.")
161 class integer_big(_column):
162 """Experimental 64 bit integer column type, currently unused.
164 TODO: this field should work fine for values up
165 to 32 bits, but greater values will not fit
166 in the XML-RPC int type, so a specific
167 get() method is needed to pass them as floats,
168 like what we do for integer functional fields.
170 _type = 'integer_big'
171 # do not reference the _symbol_* of integer class, as that would possibly
172 # unbind the lambda functions
174 _symbol_f = lambda x: int(x or 0)
175 _symbol_set = (_symbol_c, _symbol_f)
176 _symbol_get = lambda self,x: x or 0
179 def __init__(self, string='unknown', required=False, **args):
180 super(integer_big, self).__init__(string=string, required=required, **args)
183 "required=True is deprecated: making an integer_big field"
184 " `required` has no effect, as NULL values are "
185 "automatically turned into 0.")
187 class reference(_column):
189 _classic_read = False # post-process to handle missing target
191 def __init__(self, string, selection, size, **args):
192 _column.__init__(self, string=string, size=size, selection=selection, **args)
194 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
196 # copy initial values fetched previously.
198 result[value['id']] = value[name]
200 model, res_id = value[name].split(',')
201 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
202 result[value['id']] = False
208 def __init__(self, string, size, **args):
209 _column.__init__(self, string=string, size=size, **args)
210 self._symbol_set = (self._symbol_c, self._symbol_set_char)
212 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
213 def _symbol_set_char(self, symb):
215 # * we need to remove the "symb==False" from the next line BUT
216 # for now too many things rely on this broken behavior
217 # * the symb==None test should be common to all data types
218 if symb == None or symb == False:
221 # we need to convert the string to a unicode object to be able
222 # to evaluate its length (and possibly truncate it) reliably
223 u_symb = tools.ustr(symb)
225 return u_symb[:self.size].encode('utf8')
233 class float(_column):
236 _symbol_f = lambda x: __builtin__.float(x or 0.0)
237 _symbol_set = (_symbol_c, _symbol_f)
238 _symbol_get = lambda self,x: x or 0.0
240 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
241 _column.__init__(self, string=string, required=required, **args)
243 # synopsis: digits_compute(cr) -> (precision, scale)
244 self.digits_compute = digits_compute
247 "required=True is deprecated: making a float field"
248 " `required` has no effect, as NULL values are "
249 "automatically turned into 0.0.")
251 def digits_change(self, cr):
252 if self.digits_compute:
253 self.digits = self.digits_compute(cr)
255 precision, scale = self.digits
256 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
257 precision_digits=scale),
258 precision_digits=scale))
264 """ Returns the current date in a format fit for being a
265 default value to a ``date`` field.
267 This method should be provided as is to the _defaults dict, it
268 should not be called.
270 return DT.date.today().strftime(
271 tools.DEFAULT_SERVER_DATE_FORMAT)
273 class datetime(_column):
277 """ Returns the current datetime in a format fit for being a
278 default value to a ``datetime`` field.
280 This method should be provided as is to the _defaults dict, it
281 should not be called.
283 return DT.datetime.now().strftime(
284 tools.DEFAULT_SERVER_DATETIME_FORMAT)
291 """ Returns the current time in a format fit for being a
292 default value to a ``time`` field.
294 This method should be proivided as is to the _defaults dict,
295 it should not be called.
297 return DT.datetime.now().strftime(
298 tools.DEFAULT_SERVER_TIME_FORMAT)
300 class binary(_column):
303 _symbol_f = lambda symb: symb and Binary(symb) or None
304 _symbol_set = (_symbol_c, _symbol_f)
305 _symbol_get = lambda self, x: x and str(x)
307 _classic_read = False
310 def __init__(self, string='unknown', filters=None, **args):
311 _column.__init__(self, string=string, **args)
312 self.filters = filters
314 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
327 # If client is requesting only the size of the field, we return it instead
328 # of the content. Presumably a separate request will be done to read the actual
329 # content if it's needed at some point.
330 # TODO: after 6.0 we should consider returning a dict with size and content instead of
331 # having an implicit convention for the value
332 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
333 res[i] = tools.human_size(long(val))
338 class selection(_column):
341 def __init__(self, selection, string='unknown', **args):
342 _column.__init__(self, string=string, **args)
343 self.selection = selection
345 # ---------------------------------------------------------
347 # ---------------------------------------------------------
350 # Values: (0, 0, { fields }) create
351 # (1, ID, { fields }) update
352 # (2, ID) remove (delete)
353 # (3, ID) unlink one (target id or target of relation)
355 # (5) unlink all (only valid for one2many)
357 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
358 class one2one(_column):
359 _classic_read = False
360 _classic_write = True
364 def __init__(self, obj, string='unknown', **args):
365 _logger.warning("The one2one field is deprecated and doesn't work anymore.")
366 _column.__init__(self, string=string, **args)
369 def set(self, cr, obj_src, id, field, act, user=None, context=None):
372 obj = obj_src.pool.get(self._obj)
373 self._table = obj_src.pool.get(self._obj)._table
375 id_new = obj.create(cr, user, act[1])
376 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
378 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
379 id = cr.fetchone()[0]
380 obj.write(cr, user, [id], act[1], context=context)
382 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
383 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
386 class many2one(_column):
387 _classic_read = False
388 _classic_write = True
391 _symbol_f = lambda x: x or None
392 _symbol_set = (_symbol_c, _symbol_f)
394 def __init__(self, obj, string='unknown', **args):
395 _column.__init__(self, string=string, **args)
398 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
406 res[r['id']] = r[name]
408 res.setdefault(id, '')
409 obj = obj.pool.get(self._obj)
411 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
412 # we use uid=1 because the visibility of a many2one field value (just id and name)
413 # must be the access right of the parent form and not the linked object itself.
414 records = dict(obj.name_get(cr, 1,
415 list(set([x for x in res.values() if isinstance(x, (int,long))])),
418 if res[id] in records:
419 res[id] = (res[id], records[res[id]])
424 def set(self, cr, obj_src, id, field, values, user=None, context=None):
427 obj = obj_src.pool.get(self._obj)
428 self._table = obj_src.pool.get(self._obj)._table
429 if type(values) == type([]):
432 id_new = obj.create(cr, act[2])
433 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
435 obj.write(cr, [act[1]], act[2], context=context)
437 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
438 elif act[0] == 3 or act[0] == 5:
439 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
441 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
444 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
446 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
448 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
449 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
452 class one2many(_column):
453 _classic_read = False
454 _classic_write = False
458 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
459 _column.__init__(self, string=string, **args)
461 self._fields_id = fields_id
463 #one2many can't be used as condition for defaults
464 assert(self.change_default != True)
466 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
470 context = context.copy()
471 context.update(self._context)
479 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
480 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
481 if r[self._fields_id] in res:
482 res[r[self._fields_id]].append(r['id'])
485 def set(self, cr, obj, id, field, values, user=None, context=None):
490 context = context.copy()
491 context.update(self._context)
492 context['no_store_function'] = True
495 _table = obj.pool.get(self._obj)._table
496 obj = obj.pool.get(self._obj)
499 act[2][self._fields_id] = id
500 id_new = obj.create(cr, user, act[2], context=context)
501 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
503 obj.write(cr, user, [act[1]], act[2], context=context)
505 obj.unlink(cr, user, [act[1]], context=context)
507 reverse_rel = obj._all_columns.get(self._fields_id)
508 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
509 # if the model has on delete cascade, just delete the row
510 if reverse_rel.column.ondelete == "cascade":
511 obj.unlink(cr, user, [act[1]], context=context)
513 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
515 # Must use write() to recompute parent_store structure if needed
516 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
518 reverse_rel = obj._all_columns.get(self._fields_id)
519 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
520 # if the o2m has a static domain we must respect it when unlinking
521 extra_domain = self._domain if isinstance(getattr(self, '_domain', None), list) else []
522 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
523 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
524 # otherwise we only nullify the reverse foreign key column.
525 if reverse_rel.column.ondelete == "cascade":
526 obj.unlink(cr, user, ids_to_unlink, context=context)
528 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
530 # Must use write() to recompute parent_store structure if needed
531 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
533 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
534 ids3 = map(lambda x:x[0], cr.fetchall())
535 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
538 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
539 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
543 # Values: (0, 0, { fields }) create
544 # (1, ID, { fields }) update (write fields to ID)
545 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
546 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
547 # (4, ID) link (add a relationship)
549 # (6, ?, ids) set a list of links
551 class many2many(_column):
552 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
553 low-level details of the intermediary relationship table transparently.
554 A many-to-many relationship is always symmetrical, and can be declared and accessed
555 from either endpoint model.
556 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
557 or id2 (destination foreign key column name) are not specified, the system will
558 provide default values. This will by default only allow one single symmetrical
559 many-to-many relationship between the source and destination model.
560 For multiple many-to-many relationship between the same models and for
561 relationships where source and destination models are the same, ``rel``, ``id1``
562 and ``id2`` should be specified explicitly.
564 :param str obj: destination model
565 :param str rel: optional name of the intermediary relationship table. If not specified,
566 a canonical name will be derived based on the alphabetically-ordered
567 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
568 Automatic naming is not possible when the source and destination are
569 the same, for obvious ambiguity reasons.
570 :param str id1: optional name for the column holding the foreign key to the current
571 model in the relationship table. If not specified, a canonical name
572 will be derived based on the model name (in the form: `src_model_id`).
573 :param str id2: optional name for the column holding the foreign key to the destination
574 model in the relationship table. If not specified, a canonical name
575 will be derived based on the model name (in the form: `dest_model_id`)
576 :param str string: field label
578 _classic_read = False
579 _classic_write = False
583 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
586 _column.__init__(self, string=string, **args)
588 if rel and '.' in rel:
589 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
590 'You used %s, which is not a valid SQL table name.')% (string,rel))
596 def _sql_names(self, source_model):
597 """Return the SQL names defining the structure of the m2m relationship table
599 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
600 local_col is the name of the column holding the current model's FK, and
601 dest_col is the name of the column holding the destination model's FK, and
603 tbl, col1, col2 = self._rel, self._id1, self._id2
604 if not all((tbl, col1, col2)):
605 # the default table name is based on the stable alphabetical order of tables
606 dest_model = source_model.pool.get(self._obj)
607 tables = tuple(sorted([source_model._table, dest_model._table]))
609 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
610 'is not possible when source and destination models are '\
612 tbl = '%s_%s_rel' % tables
614 col1 = '%s_id' % source_model._table
616 col2 = '%s_id' % dest_model._table
617 return (tbl, col1, col2)
619 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
631 "Specifying offset at a many2many.get() is deprecated and may"
632 " produce unpredictable results.")
633 obj = model.pool.get(self._obj)
634 rel, id1, id2 = self._sql_names(model)
636 # static domains are lists, and are evaluated both here and on client-side, while string
637 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
638 # FIXME: make this distinction explicit in API!
639 domain = isinstance(self._domain, list) and self._domain or []
641 wquery = obj._where_calc(cr, user, domain, context=context)
642 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
643 from_c, where_c, where_params = wquery.get_sql()
645 where_c = ' AND ' + where_c
647 if offset or self._limit:
648 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
653 if self._limit is not None:
654 limit_str = ' LIMIT %d' % self._limit
656 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
657 FROM %(rel)s, %(from_c)s \
658 WHERE %(rel)s.%(id1)s IN %%s \
659 AND %(rel)s.%(id2)s = %(tbl)s.id \
671 'order_by': order_by,
674 cr.execute(query, [tuple(ids),] + where_params)
675 for r in cr.fetchall():
676 res[r[1]].append(r[0])
679 def set(self, cr, model, id, name, values, user=None, context=None):
684 rel, id1, id2 = self._sql_names(model)
685 obj = model.pool.get(self._obj)
687 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
690 idnew = obj.create(cr, user, act[2], context=context)
691 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
693 obj.write(cr, user, [act[1]], act[2], context=context)
695 obj.unlink(cr, user, [act[1]], context=context)
697 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
699 # following queries are in the same transaction - so should be relatively safe
700 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
701 if not cr.fetchone():
702 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
704 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
707 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
709 d1 = ' and ' + ' and '.join(d1)
712 cr.execute('delete from '+rel+' where '+id1+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2)
714 for act_nbr in act[2]:
715 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
718 # TODO: use a name_search
720 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
721 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
724 def get_nice_size(value):
726 if isinstance(value, (int,long)):
728 elif value: # this is supposed to be a string
730 return tools.human_size(size)
732 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
733 # and http://bugs.python.org/issue10066
734 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
736 def sanitize_binary_value(value):
737 # binary fields should be 7-bit ASCII base64-encoded data,
738 # but we do additional sanity checks to make sure the values
739 # are not something else that won't pass via XML-RPC
740 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
741 # these builtin types are meant to pass untouched
744 # Handle invalid bytes values that will cause problems
745 # for XML-RPC. See for more info:
746 # - http://bugs.python.org/issue10066
747 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
749 # Coercing to unicode would normally allow it to properly pass via
750 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
751 # (this works for _any_ byte values, thanks to the fallback
752 # to latin-1 passthrough encoding when decoding to unicode)
753 value = tools.ustr(value)
755 # Due to Python bug #10066 this could still yield invalid XML
756 # bytes, specifically in the low byte range, that will crash
757 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
758 # So check for low bytes values, and if any, perform
759 # base64 encoding - not very smart or useful, but this is
760 # our last resort to avoid crashing the request.
761 if invalid_xml_low_bytes.search(value):
762 # b64-encode after restoring the pure bytes with latin-1
763 # passthrough encoding
764 value = base64.b64encode(value.encode('latin-1'))
769 # ---------------------------------------------------------
771 # ---------------------------------------------------------
772 class function(_column):
774 A field whose value is computed by a function (rather
775 than being read from the database).
777 :param fnct: the callable that will compute the field value.
778 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
779 :param fnct_inv: the callable that will allow writing values in that field
780 (if not provided, the field is read-only).
781 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
783 :param str type: type of the field simulated by the function field
784 :param fnct_search: the callable that allows searching on the field
785 (if not provided, search will not return any result).
786 :param store: store computed value in database
787 (see :ref:`The *store* parameter <field-function-store>`).
788 :type store: True or dict specifying triggers for field computation
789 :param multi: name of batch for batch computation of function fields.
790 All fields with the same batch name will be computed by
791 a single function call. This changes the signature of the
794 .. _field-function-fnct: The ``fnct`` parameter
796 .. rubric:: The ``fnct`` parameter
798 The callable implementing the function field must have the following signature:
800 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
802 Implements the function field.
804 :param orm model: model to which the field belongs (should be ``self`` for
806 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
807 list of field names to compute.
808 :type field_name(s): str | [str]
809 :param arg: arbitrary value passed when declaring the function field
811 :return: mapping of ``ids`` to computed values, or if multi is provided,
812 to a map of field_names to computed values
814 The values in the returned dictionary must be of the type specified by the type
815 argument in the field declaration.
817 Here is an example with a simple function ``char`` function field::
820 def compute(self, cr, uid, ids, field_name, arg, context):
824 _columns['my_char'] = fields.function(compute, type='char', size=50)
826 # when called with ``ids=[1,2,3]``, ``compute`` could return:
830 3: False # null values should be returned explicitly too
833 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
834 of the field names that should be computed. Each value in the returned
835 dictionary must then be a dictionary mapping field names to values.
837 Here is an example where two function fields (``name`` and ``age``)
838 are both computed by a single function field::
841 def compute(self, cr, uid, ids, field_names, arg, context):
845 _columns['name'] = fields.function(compute_person_data, type='char',\
846 size=50, multi='person_data')
847 _columns[''age'] = fields.function(compute_person_data, type='integer',\
850 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
852 1: {'name': 'Bob', 'age': 23},
853 2: {'name': 'Sally', 'age': 19},
854 3: {'name': 'unknown', 'age': False}
857 .. _field-function-fnct-inv:
859 .. rubric:: The ``fnct_inv`` parameter
861 This callable implements the write operation for the function field
862 and must have the following signature:
864 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
866 Callable that implements the ``write`` operation for the function field.
868 :param orm model: model to which the field belongs (should be ``self`` for
870 :param int id: the identifier of the object to write on
871 :param str field_name: name of the field to set
872 :param fnct_inv_arg: arbitrary value passed when declaring the function field
875 When writing values for a function field, the ``multi`` parameter is ignored.
877 .. _field-function-fnct-search:
879 .. rubric:: The ``fnct_search`` parameter
881 This callable implements the search operation for the function field
882 and must have the following signature:
884 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
886 Callable that implements the ``search`` operation for the function field by expanding
887 a search criterion based on the function field into a new domain based only on
888 columns that are stored in the database.
890 :param orm model: model to which the field belongs (should be ``self`` for
892 :param orm model_again: same value as ``model`` (seriously! this is for backwards
894 :param str field_name: name of the field to search on
895 :param list criterion: domain component specifying the search criterion on the field.
897 :return: domain to use instead of ``criterion`` when performing the search.
898 This new domain must be based only on columns stored in the database, as it
899 will be used directly without any translation.
901 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
902 The most generic way to implement ``fnct_search`` is to directly search for the records that
903 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
904 ``[('id','in',[1,3,5])]``.
906 .. _field-function-store:
908 .. rubric:: The ``store`` parameter
910 The ``store`` parameter allows caching the result of the field computation in the
911 database, and defining the triggers that will invalidate that cache and force a
912 recomputation of the function field.
913 When not provided, the field is computed every time its value is read.
914 The value of ``store`` may be either ``True`` (to recompute the field value whenever
915 any field in the same record is modified), or a dictionary specifying a more
916 flexible set of recomputation triggers.
918 A trigger specification is a dictionary that maps the names of the models that
919 will trigger the computation, to a tuple describing the trigger rule, in the
923 'trigger_model': (mapping_function,
924 ['trigger_field1', 'trigger_field2'],
928 A trigger rule is defined by a 3-item tuple where:
930 * The ``mapping_function`` is defined as follows:
932 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
934 Callable that maps record ids of a trigger model to ids of the
935 corresponding records in the source model (whose field values
936 need to be recomputed).
938 :param orm model: trigger_model
939 :param list trigger_ids: ids of the records of trigger_model that were
942 :return: list of ids of the source model whose function field values
943 need to be recomputed
945 * The second item is a list of the fields who should act as triggers for
946 the computation. If an empty list is given, all fields will act as triggers.
947 * The last item is the priority, used to order the triggers when processing them
948 after any write operation on a model that has function field triggers. The
949 default priority is 10.
951 In fact, setting store = True is the same as using the following trigger dict::
954 'model_itself': (lambda self, cr, uid, ids, context: ids,
960 _classic_read = False
961 _classic_write = False
967 # multi: compute several fields in one call
969 def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, store=False, multi=False, **args):
970 _column.__init__(self, **args)
973 self._fnct_inv = fnct_inv
976 if 'relation' in args:
977 self._obj = args['relation']
979 self.digits = args.get('digits', (16,2))
980 self.digits_compute = args.get('digits_compute', None)
982 self._fnct_inv_arg = fnct_inv_arg
986 self._fnct_search = fnct_search
989 if not fnct_search and not store:
990 self.selectable = False
993 if self._type != 'many2one':
994 # m2o fields need to return tuples with name_get, not just foreign keys
995 self._classic_read = True
996 self._classic_write = True
998 self._symbol_get=lambda x:x and str(x)
1001 self._symbol_c = float._symbol_c
1002 self._symbol_f = float._symbol_f
1003 self._symbol_set = float._symbol_set
1005 if type == 'boolean':
1006 self._symbol_c = boolean._symbol_c
1007 self._symbol_f = boolean._symbol_f
1008 self._symbol_set = boolean._symbol_set
1010 if type in ['integer','integer_big']:
1011 self._symbol_c = integer._symbol_c
1012 self._symbol_f = integer._symbol_f
1013 self._symbol_set = integer._symbol_set
1015 def digits_change(self, cr):
1016 if self._type == 'float':
1017 if self.digits_compute:
1018 self.digits = self.digits_compute(cr)
1020 precision, scale = self.digits
1021 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1022 precision_digits=scale),
1023 precision_digits=scale))
1025 def search(self, cr, uid, obj, name, args, context=None):
1026 if not self._fnct_search:
1027 #CHECKME: should raise an exception
1029 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1031 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1035 field_type = obj._columns[field]._type
1036 if field_type == "many2one":
1037 # make the result a tuple if it is not already one
1038 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1039 obj_model = obj.pool.get(obj._columns[field].relation)
1040 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1041 result = (value, dict_names[value])
1043 if field_type == 'binary':
1044 if context.get('bin_size'):
1045 # client requests only the size of binary fields
1046 result = get_nice_size(value)
1047 elif not context.get('bin_raw'):
1048 result = sanitize_binary_value(value)
1050 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1051 # integer/long values greater than 2^31-1 are not supported
1052 # in pure XMLRPC, so we have to pass them as floats :-(
1053 # This is not needed for stored fields and non-functional integer
1054 # fields, as their values are constrained by the database backend
1055 # to the same 32bits signed int limit.
1056 result = float(value)
1059 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1060 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1062 if self._multi and id in result:
1063 for field, value in result[id].iteritems():
1065 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1066 elif result.get(id):
1067 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1070 def set(self, cr, obj, id, name, value, user=None, context=None):
1074 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1076 # ---------------------------------------------------------
1078 # ---------------------------------------------------------
1080 class related(function):
1081 """Field that points to some data inside another field of the current record.
1086 'foo_id': fields.many2one('my.foo', 'Foo'),
1087 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1091 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1092 self._field_get2(cr, uid, obj, context)
1093 i = len(self._arg)-1
1096 if type(sarg) in [type([]), type( (1,) )]:
1097 where = [(self._arg[i], 'in', sarg)]
1099 where = [(self._arg[i], '=', sarg)]
1101 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1103 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1105 return [(self._arg[0], 'in', sarg)]
1107 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1108 self._field_get2(cr, uid, obj, context=context)
1109 if type(ids) != type([]):
1111 objlst = obj.browse(cr, uid, ids)
1115 for i in range(len(self.arg)):
1116 if not t_data: break
1117 field_detail = self._relations[i]
1118 if not t_data[self.arg[i]]:
1119 if self._type not in ('one2many', 'many2many'):
1122 elif field_detail['type'] in ('one2many', 'many2many'):
1123 if self._type != "many2one":
1125 t_data = t_data[self.arg[i]][0]
1130 t_data = t_data[self.arg[i]]
1132 model = obj.pool.get(self._relations[-1]['object'])
1133 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1135 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1136 self._field_get2(cr, uid, obj, context)
1137 if not ids: return {}
1138 relation = obj._name
1139 if self._type in ('one2many', 'many2many'):
1140 res = dict([(i, []) for i in ids])
1142 res = {}.fromkeys(ids, False)
1144 objlst = obj.browse(cr, 1, ids, context=context)
1149 relation = obj._name
1150 for i in range(len(self.arg)):
1151 field_detail = self._relations[i]
1152 relation = field_detail['object']
1154 if not t_data[self.arg[i]]:
1160 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1161 t_data = t_data[self.arg[i]][0]
1163 t_data = t_data[self.arg[i]]
1164 if type(t_data) == type(objlst[0]):
1165 res[data.id] = t_data.id
1167 res[data.id] = t_data
1168 if self._type=='many2one':
1169 ids = filter(None, res.values())
1171 # name_get as root, as seeing the name of a related
1172 # object depends on access right of source document,
1173 # not target, so user may not have access.
1174 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1177 res[r] = (res[r], ng[res[r]])
1178 elif self._type in ('one2many', 'many2many'):
1181 res[r] = [x.id for x in res[r]]
1184 def __init__(self, *arg, **args):
1186 self._relations = []
1187 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1188 if self.store is True:
1189 # TODO: improve here to change self.store = {...} according to related objects
1192 def _field_get2(self, cr, uid, obj, context=None):
1196 obj_name = obj._name
1197 for i in range(len(self._arg)):
1198 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1204 if f.get('relation',False):
1205 obj_name = f['relation']
1206 result[-1]['relation'] = f['relation']
1207 self._relations = result
1210 class sparse(function):
1212 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1214 + For a many2many field, a list of tuples is expected.
1215 Here is the list of tuple that are accepted, with the corresponding semantics ::
1217 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1218 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1219 (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)
1220 (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)
1221 (4, ID) link to existing record with id = ID (adds a relationship)
1222 (5) unlink all (like using (3,ID) for all linked records)
1223 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1226 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1228 + For a one2many field, a lits of tuples is expected.
1229 Here is the list of tuple that are accepted, with the corresponding semantics ::
1231 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1232 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1233 (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)
1236 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1239 if self._type == 'many2many':
1240 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1243 elif self._type == 'one2many':
1246 relation_obj = obj.pool.get(self.relation)
1248 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1250 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1252 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1254 relation_obj.unlink(cr, uid, vals[1], context=context)
1255 read_value.remove(vals[1])
1260 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1261 if not type(ids) == list:
1263 records = obj.browse(cr, uid, ids, context=context)
1264 for record in records:
1265 # grab serialized value as object - already deserialized
1266 serialized = getattr(record, self.serialization_field)
1268 # simply delete the key to unset it.
1269 serialized.pop(field_name, None)
1271 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1272 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1275 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1277 records = obj.browse(cr, uid, ids, context=context)
1278 for record in records:
1279 # grab serialized value as object - already deserialized
1280 serialized = getattr(record, self.serialization_field)
1281 results[record.id] = {}
1282 for field_name in field_names:
1283 field_type = obj._columns[field_name]._type
1284 value = serialized.get(field_name, False)
1285 if field_type in ('one2many','many2many'):
1288 # filter out deleted records as superuser
1289 relation_obj = obj.pool.get(self.relation)
1290 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1291 if type(value) in (int,long) and field_type == 'many2one':
1292 relation_obj = obj.pool.get(self.relation)
1293 # check for deleted record as superuser
1294 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1296 results[record.id][field_name] = value
1299 def __init__(self, serialization_field, **kwargs):
1300 self.serialization_field = serialization_field
1301 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1305 # ---------------------------------------------------------
1307 # ---------------------------------------------------------
1309 class dummy(function):
1310 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1313 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1316 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1319 def __init__(self, *arg, **args):
1321 self._relations = []
1322 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1324 # ---------------------------------------------------------
1326 # ---------------------------------------------------------
1328 class serialized(_column):
1329 """ A field able to store an arbitrary python data structure.
1331 Note: only plain components allowed.
1334 def _symbol_set_struct(val):
1335 return simplejson.dumps(val)
1337 def _symbol_get_struct(self, val):
1338 return simplejson.loads(val or '{}')
1341 _type = 'serialized'
1344 _symbol_f = _symbol_set_struct
1345 _symbol_set = (_symbol_c, _symbol_f)
1346 _symbol_get = _symbol_get_struct
1348 # TODO: review completly this class for speed improvement
1349 class property(function):
1351 def _get_default(self, obj, cr, uid, prop_name, context=None):
1352 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1354 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1355 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1357 :param list of string prop_names: list of name of property fields for those we want the default value
1358 :return: map of property field names to their default value
1361 prop = obj.pool.get('ir.property')
1363 for prop_name in prop_names:
1364 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1367 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1368 prop = obj.pool.get('ir.property')
1369 vids = [obj._name + ',' + str(oid) for oid in ids]
1371 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1372 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1374 domain = [('res_id', 'in', vids)] + domain
1375 return prop.search(cr, uid, domain, context=context)
1377 # TODO: to rewrite more clean
1378 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1382 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1384 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1386 default_val = self._get_default(obj, cr, uid, prop_name, context)
1388 property_create = False
1389 if isinstance(default_val, openerp.osv.orm.browse_record):
1390 if default_val.id != id_val:
1391 property_create = True
1392 elif default_val != id_val:
1393 property_create = True
1396 def_id = self._field_get(cr, uid, obj._name, prop_name)
1397 company = obj.pool.get('res.company')
1398 cid = company._company_default_get(cr, uid, obj._name, def_id,
1400 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1402 prop = obj.pool.get('ir.property')
1403 return prop.create(cr, uid, {
1404 'name': propdef.name,
1406 'res_id': obj._name+','+str(id),
1408 'fields_id': def_id,
1413 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1414 prop = obj.pool.get('ir.property')
1415 # get the default values (for res_id = False) for the property fields
1416 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1418 # build the dictionary that will be returned
1421 res[id] = default_val.copy()
1423 for prop_name in prop_names:
1424 property_field = obj._all_columns.get(prop_name).column
1425 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1426 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1427 # in order to make a name_get in batch for all the ids needed.
1430 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1431 obj_reference = obj._name + ',' + str(id)
1432 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1434 res[id][prop_name] = value
1435 # Check existence as root (as seeing the name of a related
1436 # object depends on access right of source document,
1437 # not target, so user may not have access) in order to avoid
1438 # pointing on an unexisting record.
1439 if property_destination_obj:
1440 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1441 name_get_ids[id] = res[id][prop_name].id
1443 res[id][prop_name] = False
1444 if property_destination_obj:
1445 # name_get as root (as seeing the name of a related
1446 # object depends on access right of source document,
1447 # not target, so user may not have access.)
1448 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1449 # the property field is a m2o, we need to return a tuple with (id, name)
1450 for k, v in name_get_ids.iteritems():
1451 if res[k][prop_name]:
1452 res[k][prop_name] = (v , name_get_values.get(v))
1455 def _field_get(self, cr, uid, model_name, prop):
1456 if not self.field_id.get(cr.dbname):
1457 cr.execute('SELECT id \
1458 FROM ir_model_fields \
1459 WHERE name=%s AND model=%s', (prop, model_name))
1461 self.field_id[cr.dbname] = res and res[0]
1462 return self.field_id[cr.dbname]
1464 def __init__(self, obj_prop, **args):
1465 # TODO remove obj_prop parameter (use many2one type)
1467 function.__init__(self, self._fnct_read, False, self._fnct_write,
1468 obj_prop, multi='properties', **args)
1474 def field_to_dict(model, cr, user, field, context=None):
1475 """ Return a dictionary representation of a field.
1477 The string, help, and selection attributes (if any) are untranslated. This
1478 representation is the one returned by fields_get() (fields_get() will do
1483 res = {'type': field._type}
1484 # This additional attributes for M2M and function field is added
1485 # because we need to display tooltip with this additional information
1486 # when client is started in debug mode.
1487 if isinstance(field, function):
1488 res['function'] = field._fnct and field._fnct.func_name or False
1489 res['store'] = field.store
1490 if isinstance(field.store, dict):
1491 res['store'] = str(field.store)
1492 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1493 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1494 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1495 res['func_obj'] = field._obj or False
1496 if isinstance(field, many2many):
1497 (table, col1, col2) = field._sql_names(model)
1498 res['related_columns'] = [col1, col2]
1499 res['third_table'] = table
1500 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1501 'change_default', 'translate', 'help', 'select', 'selectable'):
1502 if getattr(field, arg):
1503 res[arg] = getattr(field, arg)
1504 for arg in ('digits', 'invisible', 'filters'):
1505 if getattr(field, arg, None):
1506 res[arg] = getattr(field, arg)
1509 res['string'] = field.string
1511 res['help'] = field.help
1513 if hasattr(field, 'selection'):
1514 if isinstance(field.selection, (tuple, list)):
1515 res['selection'] = field.selection
1517 # call the 'dynamic selection' function
1518 res['selection'] = field.selection(model, cr, user, context)
1519 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1520 res['relation'] = field._obj
1521 res['domain'] = field._domain
1522 res['context'] = field._context
1524 if isinstance(field, one2many):
1525 res['relation_field'] = field._fields_id
1530 class column_info(object):
1531 """Struct containing details about an osv column, either one local to
1532 its model, or one inherited via _inherits.
1534 :attr name: name of the column
1535 :attr column: column instance, subclass of osv.fields._column
1536 :attr parent_model: if the column is inherited, name of the model
1537 that contains it, None for local columns.
1538 :attr parent_column: the name of the column containing the m2o
1539 relationship to the parent model that contains
1540 this column, None for local columns.
1541 :attr original_parent: if the column is inherited, name of the original
1542 parent model that contains it i.e in case of multilevel
1543 inheritence, None for local columns.
1545 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1547 self.column = column
1548 self.parent_model = parent_model
1549 self.parent_column = parent_column
1550 self.original_parent = original_parent
1552 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: