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
42 from psycopg2 import Binary
45 import openerp.netsvc as netsvc
46 import openerp.tools as tools
47 from openerp.tools.translate import _
48 from openerp.tools import float_round, float_repr
51 _logger = logging.getLogger(__name__)
53 def _symbol_set(symb):
54 if symb == None or symb == False:
56 elif isinstance(symb, unicode):
57 return symb.encode('utf-8')
61 class _column(object):
62 """ Base of all fields, a database column
64 An instance of this object is a *description* of a database column. It will
65 not hold any data, but only provide the methods to manipulate data of an
66 ORM record or even prepare/update the database to hold such a field of data.
76 _symbol_f = _symbol_set
77 _symbol_set = (_symbol_c, _symbol_f)
80 # used to hide a certain field type in the list of field types
83 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):
86 The 'manual' keyword argument specifies if the field is a custom one.
87 It corresponds to the 'state' column in ir_model_fields.
94 self.states = states or {}
96 self.readonly = readonly
97 self.required = required
99 self.help = args.get('help', '')
100 self.priority = priority
101 self.change_default = change_default
102 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
103 self.translate = translate
104 self._domain = domain
105 self._context = context
111 self.selectable = True
112 self.group_operator = args.get('group_operator', False)
115 setattr(self, a, args[a])
120 def set(self, cr, obj, id, name, value, user=None, context=None):
121 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
123 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
124 raise Exception(_('undefined get method !'))
126 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
127 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
128 res = obj.read(cr, uid, ids, [name], context=context)
129 return [x[name] for x in res]
132 # ---------------------------------------------------------
134 # ---------------------------------------------------------
135 class boolean(_column):
138 _symbol_f = lambda x: x and 'True' or 'False'
139 _symbol_set = (_symbol_c, _symbol_f)
141 def __init__(self, string='unknown', required=False, **args):
142 super(boolean, self).__init__(string=string, required=required, **args)
145 "required=True is deprecated: making a boolean field"
146 " `required` has no effect, as NULL values are "
147 "automatically turned into False.")
149 class integer(_column):
152 _symbol_f = lambda x: int(x or 0)
153 _symbol_set = (_symbol_c, _symbol_f)
154 _symbol_get = lambda self,x: x or 0
156 def __init__(self, string='unknown', required=False, **args):
157 super(integer, self).__init__(string=string, required=required, **args)
160 "required=True is deprecated: making an integer field"
161 " `required` has no effect, as NULL values are "
162 "automatically turned into 0.")
164 class integer_big(_column):
165 """Experimental 64 bit integer column type, currently unused.
167 TODO: this field should work fine for values up
168 to 32 bits, but greater values will not fit
169 in the XML-RPC int type, so a specific
170 get() method is needed to pass them as floats,
171 like what we do for integer functional fields.
173 _type = 'integer_big'
174 # do not reference the _symbol_* of integer class, as that would possibly
175 # unbind the lambda functions
177 _symbol_f = lambda x: int(x or 0)
178 _symbol_set = (_symbol_c, _symbol_f)
179 _symbol_get = lambda self,x: x or 0
182 def __init__(self, string='unknown', required=False, **args):
183 super(integer_big, self).__init__(string=string, required=required, **args)
186 "required=True is deprecated: making an integer_big field"
187 " `required` has no effect, as NULL values are "
188 "automatically turned into 0.")
190 class reference(_column):
192 _classic_read = False # post-process to handle missing target
194 def __init__(self, string, selection, size, **args):
195 _column.__init__(self, string=string, size=size, selection=selection, **args)
197 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
199 # copy initial values fetched previously.
201 result[value['id']] = value[name]
203 model, res_id = value[name].split(',')
204 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
205 result[value['id']] = False
211 def __init__(self, string, size, **args):
212 _column.__init__(self, string=string, size=size, **args)
213 self._symbol_set = (self._symbol_c, self._symbol_set_char)
215 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
216 def _symbol_set_char(self, symb):
218 # * we need to remove the "symb==False" from the next line BUT
219 # for now too many things rely on this broken behavior
220 # * the symb==None test should be common to all data types
221 if symb == None or symb == False:
224 # we need to convert the string to a unicode object to be able
225 # to evaluate its length (and possibly truncate it) reliably
226 u_symb = tools.ustr(symb)
228 return u_symb[:self.size].encode('utf8')
236 class float(_column):
239 _symbol_f = lambda x: __builtin__.float(x or 0.0)
240 _symbol_set = (_symbol_c, _symbol_f)
241 _symbol_get = lambda self,x: x or 0.0
243 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
244 _column.__init__(self, string=string, required=required, **args)
246 # synopsis: digits_compute(cr) -> (precision, scale)
247 self.digits_compute = digits_compute
250 "required=True is deprecated: making a float field"
251 " `required` has no effect, as NULL values are "
252 "automatically turned into 0.0.")
254 def digits_change(self, cr):
255 if self.digits_compute:
256 self.digits = self.digits_compute(cr)
258 precision, scale = self.digits
259 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
260 precision_digits=scale),
261 precision_digits=scale))
267 """ Returns the current date in a format fit for being a
268 default value to a ``date`` field.
270 This method should be provided as is to the _defaults dict, it
271 should not be called.
273 return DT.date.today().strftime(
274 tools.DEFAULT_SERVER_DATE_FORMAT)
276 class datetime(_column):
280 """ Returns the current datetime in a format fit for being a
281 default value to a ``datetime`` field.
283 This method should be provided as is to the _defaults dict, it
284 should not be called.
286 return DT.datetime.now().strftime(
287 tools.DEFAULT_SERVER_DATETIME_FORMAT)
294 """ Returns the current time in a format fit for being a
295 default value to a ``time`` field.
297 This method should be proivided as is to the _defaults dict,
298 it should not be called.
300 return DT.datetime.now().strftime(
301 tools.DEFAULT_SERVER_TIME_FORMAT)
303 class binary(_column):
306 _symbol_f = lambda symb: symb and Binary(symb) or None
307 _symbol_set = (_symbol_c, _symbol_f)
308 _symbol_get = lambda self, x: x and str(x)
310 _classic_read = False
313 def __init__(self, string='unknown', filters=None, **args):
314 _column.__init__(self, string=string, **args)
315 self.filters = filters
317 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
330 # If client is requesting only the size of the field, we return it instead
331 # of the content. Presumably a separate request will be done to read the actual
332 # content if it's needed at some point.
333 # TODO: after 6.0 we should consider returning a dict with size and content instead of
334 # having an implicit convention for the value
335 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
336 res[i] = tools.human_size(long(val))
341 class selection(_column):
344 def __init__(self, selection, string='unknown', **args):
345 _column.__init__(self, string=string, **args)
346 self.selection = selection
348 # ---------------------------------------------------------
350 # ---------------------------------------------------------
353 # Values: (0, 0, { fields }) create
354 # (1, ID, { fields }) update
355 # (2, ID) remove (delete)
356 # (3, ID) unlink one (target id or target of relation)
358 # (5) unlink all (only valid for one2many)
360 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
361 class one2one(_column):
362 _classic_read = False
363 _classic_write = True
367 def __init__(self, obj, string='unknown', **args):
368 _logger.warning("The one2one field is deprecated and doesn't work anymore.")
369 _column.__init__(self, string=string, **args)
372 def set(self, cr, obj_src, id, field, act, user=None, context=None):
375 obj = obj_src.pool.get(self._obj)
376 self._table = obj_src.pool.get(self._obj)._table
378 id_new = obj.create(cr, user, act[1])
379 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
381 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
382 id = cr.fetchone()[0]
383 obj.write(cr, user, [id], act[1], context=context)
385 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
386 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
389 class many2one(_column):
390 _classic_read = False
391 _classic_write = True
394 _symbol_f = lambda x: x or None
395 _symbol_set = (_symbol_c, _symbol_f)
397 def __init__(self, obj, string='unknown', **args):
398 _column.__init__(self, string=string, **args)
401 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
409 res[r['id']] = r[name]
411 res.setdefault(id, '')
412 obj = obj.pool.get(self._obj)
414 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
415 # we use uid=1 because the visibility of a many2one field value (just id and name)
416 # must be the access right of the parent form and not the linked object itself.
417 records = dict(obj.name_get(cr, 1,
418 list(set([x for x in res.values() if isinstance(x, (int,long))])),
421 if res[id] in records:
422 res[id] = (res[id], records[res[id]])
427 def set(self, cr, obj_src, id, field, values, user=None, context=None):
430 obj = obj_src.pool.get(self._obj)
431 self._table = obj_src.pool.get(self._obj)._table
432 if type(values) == type([]):
435 id_new = obj.create(cr, act[2])
436 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
438 obj.write(cr, [act[1]], act[2], context=context)
440 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
441 elif act[0] == 3 or act[0] == 5:
442 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
444 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
447 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
449 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
451 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
452 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
455 class one2many(_column):
456 _classic_read = False
457 _classic_write = False
461 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
462 _column.__init__(self, string=string, **args)
464 self._fields_id = fields_id
466 #one2many can't be used as condition for defaults
467 assert(self.change_default != True)
469 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
473 context = context.copy()
474 context.update(self._context)
482 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
483 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
484 if r[self._fields_id] in res:
485 res[r[self._fields_id]].append(r['id'])
488 def set(self, cr, obj, id, field, values, user=None, context=None):
493 context = context.copy()
494 context.update(self._context)
495 context['no_store_function'] = True
498 _table = obj.pool.get(self._obj)._table
499 obj = obj.pool.get(self._obj)
502 act[2][self._fields_id] = id
503 id_new = obj.create(cr, user, act[2], context=context)
504 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
506 obj.write(cr, user, [act[1]], act[2], context=context)
508 obj.unlink(cr, user, [act[1]], context=context)
510 reverse_rel = obj._all_columns.get(self._fields_id)
511 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
512 # if the model has on delete cascade, just delete the row
513 if reverse_rel.column.ondelete == "cascade":
514 obj.unlink(cr, user, [act[1]], context=context)
516 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
518 # Must use write() to recompute parent_store structure if needed
519 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
521 reverse_rel = obj._all_columns.get(self._fields_id)
522 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
523 # if the o2m has a static domain we must respect it when unlinking
524 extra_domain = self._domain if isinstance(getattr(self, '_domain', None), list) else []
525 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
526 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
527 # otherwise we only nullify the reverse foreign key column.
528 if reverse_rel.column.ondelete == "cascade":
529 obj.unlink(cr, user, ids_to_unlink, context=context)
531 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
533 # Must use write() to recompute parent_store structure if needed
534 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
536 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
537 ids3 = map(lambda x:x[0], cr.fetchall())
538 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
541 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
542 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
546 # Values: (0, 0, { fields }) create
547 # (1, ID, { fields }) update (write fields to ID)
548 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
549 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
550 # (4, ID) link (add a relationship)
552 # (6, ?, ids) set a list of links
554 class many2many(_column):
555 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
556 low-level details of the intermediary relationship table transparently.
557 A many-to-many relationship is always symmetrical, and can be declared and accessed
558 from either endpoint model.
559 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
560 or id2 (destination foreign key column name) are not specified, the system will
561 provide default values. This will by default only allow one single symmetrical
562 many-to-many relationship between the source and destination model.
563 For multiple many-to-many relationship between the same models and for
564 relationships where source and destination models are the same, ``rel``, ``id1``
565 and ``id2`` should be specified explicitly.
567 :param str obj: destination model
568 :param str rel: optional name of the intermediary relationship table. If not specified,
569 a canonical name will be derived based on the alphabetically-ordered
570 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
571 Automatic naming is not possible when the source and destination are
572 the same, for obvious ambiguity reasons.
573 :param str id1: optional name for the column holding the foreign key to the current
574 model in the relationship table. If not specified, a canonical name
575 will be derived based on the model name (in the form: `src_model_id`).
576 :param str id2: optional name for the column holding the foreign key to the destination
577 model in the relationship table. If not specified, a canonical name
578 will be derived based on the model name (in the form: `dest_model_id`)
579 :param str string: field label
581 _classic_read = False
582 _classic_write = False
586 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
589 _column.__init__(self, string=string, **args)
591 if rel and '.' in rel:
592 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
593 'You used %s, which is not a valid SQL table name.')% (string,rel))
599 def _sql_names(self, source_model):
600 """Return the SQL names defining the structure of the m2m relationship table
602 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
603 local_col is the name of the column holding the current model's FK, and
604 dest_col is the name of the column holding the destination model's FK, and
606 tbl, col1, col2 = self._rel, self._id1, self._id2
607 if not all((tbl, col1, col2)):
608 # the default table name is based on the stable alphabetical order of tables
609 dest_model = source_model.pool.get(self._obj)
610 tables = tuple(sorted([source_model._table, dest_model._table]))
612 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
613 'is not possible when source and destination models are '\
615 tbl = '%s_%s_rel' % tables
617 col1 = '%s_id' % source_model._table
619 col2 = '%s_id' % dest_model._table
620 return (tbl, col1, col2)
622 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
634 "Specifying offset at a many2many.get() is deprecated and may"
635 " produce unpredictable results.")
636 obj = model.pool.get(self._obj)
637 rel, id1, id2 = self._sql_names(model)
639 # static domains are lists, and are evaluated both here and on client-side, while string
640 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
641 # FIXME: make this distinction explicit in API!
642 domain = isinstance(self._domain, list) and self._domain or []
644 wquery = obj._where_calc(cr, user, domain, context=context)
645 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
646 from_c, where_c, where_params = wquery.get_sql()
648 where_c = ' AND ' + where_c
650 if offset or self._limit:
651 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
656 if self._limit is not None:
657 limit_str = ' LIMIT %d' % self._limit
659 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
660 FROM %(rel)s, %(from_c)s \
661 WHERE %(rel)s.%(id1)s IN %%s \
662 AND %(rel)s.%(id2)s = %(tbl)s.id \
674 'order_by': order_by,
677 cr.execute(query, [tuple(ids),] + where_params)
678 for r in cr.fetchall():
679 res[r[1]].append(r[0])
682 def set(self, cr, model, id, name, values, user=None, context=None):
687 rel, id1, id2 = self._sql_names(model)
688 obj = model.pool.get(self._obj)
690 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
693 idnew = obj.create(cr, user, act[2], context=context)
694 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
696 obj.write(cr, user, [act[1]], act[2], context=context)
698 obj.unlink(cr, user, [act[1]], context=context)
700 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
702 # following queries are in the same transaction - so should be relatively safe
703 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
704 if not cr.fetchone():
705 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
707 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
710 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
712 d1 = ' and ' + ' and '.join(d1)
715 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)
717 for act_nbr in act[2]:
718 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
721 # TODO: use a name_search
723 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
724 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
727 def get_nice_size(value):
729 if isinstance(value, (int,long)):
731 elif value: # this is supposed to be a string
733 return tools.human_size(size)
735 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
736 # and http://bugs.python.org/issue10066
737 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
739 def sanitize_binary_value(value):
740 # binary fields should be 7-bit ASCII base64-encoded data,
741 # but we do additional sanity checks to make sure the values
742 # are not something else that won't pass via XML-RPC
743 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
744 # these builtin types are meant to pass untouched
747 # Handle invalid bytes values that will cause problems
748 # for XML-RPC. See for more info:
749 # - http://bugs.python.org/issue10066
750 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
752 # Coercing to unicode would normally allow it to properly pass via
753 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
754 # (this works for _any_ byte values, thanks to the fallback
755 # to latin-1 passthrough encoding when decoding to unicode)
756 value = tools.ustr(value)
758 # Due to Python bug #10066 this could still yield invalid XML
759 # bytes, specifically in the low byte range, that will crash
760 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
761 # So check for low bytes values, and if any, perform
762 # base64 encoding - not very smart or useful, but this is
763 # our last resort to avoid crashing the request.
764 if invalid_xml_low_bytes.search(value):
765 # b64-encode after restoring the pure bytes with latin-1
766 # passthrough encoding
767 value = base64.b64encode(value.encode('latin-1'))
772 # ---------------------------------------------------------
774 # ---------------------------------------------------------
775 class function(_column):
777 A field whose value is computed by a function (rather
778 than being read from the database).
780 :param fnct: the callable that will compute the field value.
781 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
782 :param fnct_inv: the callable that will allow writing values in that field
783 (if not provided, the field is read-only).
784 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
786 :param str type: type of the field simulated by the function field
787 :param fnct_search: the callable that allows searching on the field
788 (if not provided, search will not return any result).
789 :param store: store computed value in database
790 (see :ref:`The *store* parameter <field-function-store>`).
791 :type store: True or dict specifying triggers for field computation
792 :param multi: name of batch for batch computation of function fields.
793 All fields with the same batch name will be computed by
794 a single function call. This changes the signature of the
797 .. _field-function-fnct: The ``fnct`` parameter
799 .. rubric:: The ``fnct`` parameter
801 The callable implementing the function field must have the following signature:
803 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
805 Implements the function field.
807 :param orm model: model to which the field belongs (should be ``self`` for
809 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
810 list of field names to compute.
811 :type field_name(s): str | [str]
812 :param arg: arbitrary value passed when declaring the function field
814 :return: mapping of ``ids`` to computed values, or if multi is provided,
815 to a map of field_names to computed values
817 The values in the returned dictionary must be of the type specified by the type
818 argument in the field declaration.
820 Here is an example with a simple function ``char`` function field::
823 def compute(self, cr, uid, ids, field_name, arg, context):
827 _columns['my_char'] = fields.function(compute, type='char', size=50)
829 # when called with ``ids=[1,2,3]``, ``compute`` could return:
833 3: False # null values should be returned explicitly too
836 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
837 of the field names that should be computed. Each value in the returned
838 dictionary must then be a dictionary mapping field names to values.
840 Here is an example where two function fields (``name`` and ``age``)
841 are both computed by a single function field::
844 def compute(self, cr, uid, ids, field_names, arg, context):
848 _columns['name'] = fields.function(compute_person_data, type='char',\
849 size=50, multi='person_data')
850 _columns[''age'] = fields.function(compute_person_data, type='integer',\
853 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
855 1: {'name': 'Bob', 'age': 23},
856 2: {'name': 'Sally', 'age': 19},
857 3: {'name': 'unknown', 'age': False}
860 .. _field-function-fnct-inv:
862 .. rubric:: The ``fnct_inv`` parameter
864 This callable implements the write operation for the function field
865 and must have the following signature:
867 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
869 Callable that implements the ``write`` operation for the function field.
871 :param orm model: model to which the field belongs (should be ``self`` for
873 :param int id: the identifier of the object to write on
874 :param str field_name: name of the field to set
875 :param fnct_inv_arg: arbitrary value passed when declaring the function field
878 When writing values for a function field, the ``multi`` parameter is ignored.
880 .. _field-function-fnct-search:
882 .. rubric:: The ``fnct_search`` parameter
884 This callable implements the search operation for the function field
885 and must have the following signature:
887 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
889 Callable that implements the ``search`` operation for the function field by expanding
890 a search criterion based on the function field into a new domain based only on
891 columns that are stored in the database.
893 :param orm model: model to which the field belongs (should be ``self`` for
895 :param orm model_again: same value as ``model`` (seriously! this is for backwards
897 :param str field_name: name of the field to search on
898 :param list criterion: domain component specifying the search criterion on the field.
900 :return: domain to use instead of ``criterion`` when performing the search.
901 This new domain must be based only on columns stored in the database, as it
902 will be used directly without any translation.
904 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
905 The most generic way to implement ``fnct_search`` is to directly search for the records that
906 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
907 ``[('id','in',[1,3,5])]``.
909 .. _field-function-store:
911 .. rubric:: The ``store`` parameter
913 The ``store`` parameter allows caching the result of the field computation in the
914 database, and defining the triggers that will invalidate that cache and force a
915 recomputation of the function field.
916 When not provided, the field is computed every time its value is read.
917 The value of ``store`` may be either ``True`` (to recompute the field value whenever
918 any field in the same record is modified), or a dictionary specifying a more
919 flexible set of recomputation triggers.
921 A trigger specification is a dictionary that maps the names of the models that
922 will trigger the computation, to a tuple describing the trigger rule, in the
926 'trigger_model': (mapping_function,
927 ['trigger_field1', 'trigger_field2'],
931 A trigger rule is defined by a 3-item tuple where:
933 * The ``mapping_function`` is defined as follows:
935 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
937 Callable that maps record ids of a trigger model to ids of the
938 corresponding records in the source model (whose field values
939 need to be recomputed).
941 :param orm model: trigger_model
942 :param list trigger_ids: ids of the records of trigger_model that were
945 :return: list of ids of the source model whose function field values
946 need to be recomputed
948 * The second item is a list of the fields who should act as triggers for
949 the computation. If an empty list is given, all fields will act as triggers.
950 * The last item is the priority, used to order the triggers when processing them
951 after any write operation on a model that has function field triggers. The
952 default priority is 10.
954 In fact, setting store = True is the same as using the following trigger dict::
957 'model_itself': (lambda self, cr, uid, ids, context: ids,
963 _classic_read = False
964 _classic_write = False
970 # multi: compute several fields in one call
972 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):
973 _column.__init__(self, **args)
976 self._fnct_inv = fnct_inv
979 if 'relation' in args:
980 self._obj = args['relation']
982 self.digits = args.get('digits', (16,2))
983 self.digits_compute = args.get('digits_compute', None)
985 self._fnct_inv_arg = fnct_inv_arg
989 self._fnct_search = fnct_search
992 if not fnct_search and not store:
993 self.selectable = False
996 if self._type != 'many2one':
997 # m2o fields need to return tuples with name_get, not just foreign keys
998 self._classic_read = True
999 self._classic_write = True
1001 self._symbol_get=lambda x:x and str(x)
1004 self._symbol_c = float._symbol_c
1005 self._symbol_f = float._symbol_f
1006 self._symbol_set = float._symbol_set
1008 if type == 'boolean':
1009 self._symbol_c = boolean._symbol_c
1010 self._symbol_f = boolean._symbol_f
1011 self._symbol_set = boolean._symbol_set
1013 if type in ['integer','integer_big']:
1014 self._symbol_c = integer._symbol_c
1015 self._symbol_f = integer._symbol_f
1016 self._symbol_set = integer._symbol_set
1018 def digits_change(self, cr):
1019 if self._type == 'float':
1020 if self.digits_compute:
1021 self.digits = self.digits_compute(cr)
1023 precision, scale = self.digits
1024 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1025 precision_digits=scale),
1026 precision_digits=scale))
1028 def search(self, cr, uid, obj, name, args, context=None):
1029 if not self._fnct_search:
1030 #CHECKME: should raise an exception
1032 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1034 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1038 field_type = obj._columns[field]._type
1039 if field_type == "many2one":
1040 # make the result a tuple if it is not already one
1041 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1042 obj_model = obj.pool.get(obj._columns[field].relation)
1043 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1044 result = (value, dict_names[value])
1046 if field_type == 'binary':
1047 if context.get('bin_size'):
1048 # client requests only the size of binary fields
1049 result = get_nice_size(value)
1050 elif not context.get('bin_raw'):
1051 result = sanitize_binary_value(value)
1053 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1054 # integer/long values greater than 2^31-1 are not supported
1055 # in pure XMLRPC, so we have to pass them as floats :-(
1056 # This is not needed for stored fields and non-functional integer
1057 # fields, as their values are constrained by the database backend
1058 # to the same 32bits signed int limit.
1059 result = float(value)
1062 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1063 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1065 if self._multi and id in result:
1066 for field, value in result[id].iteritems():
1068 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1069 elif result.get(id):
1070 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1073 def set(self, cr, obj, id, name, value, user=None, context=None):
1077 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1079 # ---------------------------------------------------------
1081 # ---------------------------------------------------------
1083 class related(function):
1084 """Field that points to some data inside another field of the current record.
1089 'foo_id': fields.many2one('my.foo', 'Foo'),
1090 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1094 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1095 self._field_get2(cr, uid, obj, context)
1096 i = len(self._arg)-1
1099 if type(sarg) in [type([]), type( (1,) )]:
1100 where = [(self._arg[i], 'in', sarg)]
1102 where = [(self._arg[i], '=', sarg)]
1104 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1106 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1108 return [(self._arg[0], 'in', sarg)]
1110 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1111 self._field_get2(cr, uid, obj, context=context)
1112 if type(ids) != type([]):
1114 objlst = obj.browse(cr, uid, ids)
1118 for i in range(len(self.arg)):
1119 if not t_data: break
1120 field_detail = self._relations[i]
1121 if not t_data[self.arg[i]]:
1122 if self._type not in ('one2many', 'many2many'):
1125 elif field_detail['type'] in ('one2many', 'many2many'):
1126 if self._type != "many2one":
1128 t_data = t_data[self.arg[i]][0]
1133 t_data = t_data[self.arg[i]]
1135 model = obj.pool.get(self._relations[-1]['object'])
1136 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1138 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1139 self._field_get2(cr, uid, obj, context)
1140 if not ids: return {}
1141 relation = obj._name
1142 if self._type in ('one2many', 'many2many'):
1143 res = dict([(i, []) for i in ids])
1145 res = {}.fromkeys(ids, False)
1147 objlst = obj.browse(cr, 1, ids, context=context)
1152 relation = obj._name
1153 for i in range(len(self.arg)):
1154 field_detail = self._relations[i]
1155 relation = field_detail['object']
1157 if not t_data[self.arg[i]]:
1163 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1164 t_data = t_data[self.arg[i]][0]
1166 t_data = t_data[self.arg[i]]
1167 if type(t_data) == type(objlst[0]):
1168 res[data.id] = t_data.id
1170 res[data.id] = t_data
1171 if self._type=='many2one':
1172 ids = filter(None, res.values())
1174 # name_get as root, as seeing the name of a related
1175 # object depends on access right of source document,
1176 # not target, so user may not have access.
1177 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1180 res[r] = (res[r], ng[res[r]])
1181 elif self._type in ('one2many', 'many2many'):
1184 res[r] = [x.id for x in res[r]]
1187 def __init__(self, *arg, **args):
1189 self._relations = []
1190 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1191 if self.store is True:
1192 # TODO: improve here to change self.store = {...} according to related objects
1195 def _field_get2(self, cr, uid, obj, context=None):
1199 obj_name = obj._name
1200 for i in range(len(self._arg)):
1201 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1207 if f.get('relation',False):
1208 obj_name = f['relation']
1209 result[-1]['relation'] = f['relation']
1210 self._relations = result
1213 class sparse(function):
1215 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1217 + For a many2many field, a list of tuples is expected.
1218 Here is the list of tuple that are accepted, with the corresponding semantics ::
1220 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1221 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1222 (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)
1223 (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)
1224 (4, ID) link to existing record with id = ID (adds a relationship)
1225 (5) unlink all (like using (3,ID) for all linked records)
1226 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1229 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1231 + For a one2many field, a lits of tuples is expected.
1232 Here is the list of tuple that are accepted, with the corresponding semantics ::
1234 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1235 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1236 (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)
1239 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1242 if self._type == 'many2many':
1243 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1246 elif self._type == 'one2many':
1249 relation_obj = obj.pool.get(self.relation)
1251 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1253 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1255 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1257 relation_obj.unlink(cr, uid, vals[1], context=context)
1258 read_value.remove(vals[1])
1263 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1264 if not type(ids) == list:
1266 records = obj.browse(cr, uid, ids, context=context)
1267 for record in records:
1268 # grab serialized value as object - already deserialized
1269 serialized = getattr(record, self.serialization_field)
1271 # simply delete the key to unset it.
1272 serialized.pop(field_name, None)
1274 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1275 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1278 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1280 records = obj.browse(cr, uid, ids, context=context)
1281 for record in records:
1282 # grab serialized value as object - already deserialized
1283 serialized = getattr(record, self.serialization_field)
1284 results[record.id] = {}
1285 for field_name in field_names:
1286 field_type = obj._columns[field_name]._type
1287 value = serialized.get(field_name, False)
1288 if field_type in ('one2many','many2many'):
1291 # filter out deleted records as superuser
1292 relation_obj = obj.pool.get(self.relation)
1293 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1294 if type(value) in (int,long) and field_type == 'many2one':
1295 relation_obj = obj.pool.get(self.relation)
1296 # check for deleted record as superuser
1297 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1299 results[record.id][field_name] = value
1302 def __init__(self, serialization_field, **kwargs):
1303 self.serialization_field = serialization_field
1304 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1308 # ---------------------------------------------------------
1310 # ---------------------------------------------------------
1312 class dummy(function):
1313 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1316 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1319 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1322 def __init__(self, *arg, **args):
1324 self._relations = []
1325 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1327 # ---------------------------------------------------------
1329 # ---------------------------------------------------------
1331 class serialized(_column):
1332 """ A field able to store an arbitrary python data structure.
1334 Note: only plain components allowed.
1337 def _symbol_set_struct(val):
1338 return simplejson.dumps(val)
1340 def _symbol_get_struct(self, val):
1341 return simplejson.loads(val or '{}')
1344 _type = 'serialized'
1347 _symbol_f = _symbol_set_struct
1348 _symbol_set = (_symbol_c, _symbol_f)
1349 _symbol_get = _symbol_get_struct
1351 # TODO: review completly this class for speed improvement
1352 class property(function):
1354 def _get_default(self, obj, cr, uid, prop_name, context=None):
1355 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1357 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1358 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1360 :param list of string prop_names: list of name of property fields for those we want the default value
1361 :return: map of property field names to their default value
1364 prop = obj.pool.get('ir.property')
1366 for prop_name in prop_names:
1367 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1370 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1371 prop = obj.pool.get('ir.property')
1372 vids = [obj._name + ',' + str(oid) for oid in ids]
1374 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1375 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1377 domain = [('res_id', 'in', vids)] + domain
1378 return prop.search(cr, uid, domain, context=context)
1380 # TODO: to rewrite more clean
1381 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1385 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1387 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1389 default_val = self._get_default(obj, cr, uid, prop_name, context)
1391 property_create = False
1392 if isinstance(default_val, openerp.osv.orm.browse_record):
1393 if default_val.id != id_val:
1394 property_create = True
1395 elif default_val != id_val:
1396 property_create = True
1399 def_id = self._field_get(cr, uid, obj._name, prop_name)
1400 company = obj.pool.get('res.company')
1401 cid = company._company_default_get(cr, uid, obj._name, def_id,
1403 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1405 prop = obj.pool.get('ir.property')
1406 return prop.create(cr, uid, {
1407 'name': propdef.name,
1409 'res_id': obj._name+','+str(id),
1411 'fields_id': def_id,
1416 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1417 prop = obj.pool.get('ir.property')
1418 # get the default values (for res_id = False) for the property fields
1419 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1421 # build the dictionary that will be returned
1424 res[id] = default_val.copy()
1426 for prop_name in prop_names:
1427 property_field = obj._all_columns.get(prop_name).column
1428 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1429 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1430 # in order to make a name_get in batch for all the ids needed.
1433 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1434 obj_reference = obj._name + ',' + str(id)
1435 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1437 res[id][prop_name] = value
1438 # Check existence as root (as seeing the name of a related
1439 # object depends on access right of source document,
1440 # not target, so user may not have access) in order to avoid
1441 # pointing on an unexisting record.
1442 if property_destination_obj:
1443 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1444 name_get_ids[id] = res[id][prop_name].id
1446 res[id][prop_name] = False
1447 if property_destination_obj:
1448 # name_get as root (as seeing the name of a related
1449 # object depends on access right of source document,
1450 # not target, so user may not have access.)
1451 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1452 # the property field is a m2o, we need to return a tuple with (id, name)
1453 for k, v in name_get_ids.iteritems():
1454 if res[k][prop_name]:
1455 res[k][prop_name] = (v , name_get_values.get(v))
1458 def _field_get(self, cr, uid, model_name, prop):
1459 if not self.field_id.get(cr.dbname):
1460 cr.execute('SELECT id \
1461 FROM ir_model_fields \
1462 WHERE name=%s AND model=%s', (prop, model_name))
1464 self.field_id[cr.dbname] = res and res[0]
1465 return self.field_id[cr.dbname]
1467 def __init__(self, obj_prop, **args):
1468 # TODO remove obj_prop parameter (use many2one type)
1470 function.__init__(self, self._fnct_read, False, self._fnct_write,
1471 obj_prop, multi='properties', **args)
1477 def field_to_dict(model, cr, user, field, context=None):
1478 """ Return a dictionary representation of a field.
1480 The string, help, and selection attributes (if any) are untranslated. This
1481 representation is the one returned by fields_get() (fields_get() will do
1486 res = {'type': field._type}
1487 # This additional attributes for M2M and function field is added
1488 # because we need to display tooltip with this additional information
1489 # when client is started in debug mode.
1490 if isinstance(field, function):
1491 res['function'] = field._fnct and field._fnct.func_name or False
1492 res['store'] = field.store
1493 if isinstance(field.store, dict):
1494 res['store'] = str(field.store)
1495 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1496 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1497 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1498 res['func_obj'] = field._obj or False
1499 if isinstance(field, many2many):
1500 (table, col1, col2) = field._sql_names(model)
1501 res['related_columns'] = [col1, col2]
1502 res['third_table'] = table
1503 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1504 'change_default', 'translate', 'help', 'select', 'selectable'):
1505 if getattr(field, arg):
1506 res[arg] = getattr(field, arg)
1507 for arg in ('digits', 'invisible', 'filters'):
1508 if getattr(field, arg, None):
1509 res[arg] = getattr(field, arg)
1512 res['string'] = field.string
1514 res['help'] = field.help
1516 if hasattr(field, 'selection'):
1517 if isinstance(field.selection, (tuple, list)):
1518 res['selection'] = field.selection
1520 # call the 'dynamic selection' function
1521 res['selection'] = field.selection(model, cr, user, context)
1522 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1523 res['relation'] = field._obj
1524 res['domain'] = field._domain
1525 res['context'] = field._context
1527 if isinstance(field, one2many):
1528 res['relation_field'] = field._fields_id
1533 class column_info(object):
1534 """Struct containing details about an osv column, either one local to
1535 its model, or one inherited via _inherits.
1537 :attr name: name of the column
1538 :attr column: column instance, subclass of osv.fields._column
1539 :attr parent_model: if the column is inherited, name of the model
1540 that contains it, None for local columns.
1541 :attr parent_column: the name of the column containing the m2o
1542 relationship to the parent model that contains
1543 this column, None for local columns.
1544 :attr original_parent: if the column is inherited, name of the original
1545 parent model that contains it i.e in case of multilevel
1546 inheritence, None for local columns.
1548 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1550 self.column = column
1551 self.parent_model = parent_model
1552 self.parent_column = parent_column
1553 self.original_parent = original_parent
1555 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: