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 _
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 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):
80 The 'manual' keyword argument specifies if the field is a custom one.
81 It corresponds to the 'state' column in ir_model_fields.
88 self.states = states or {}
90 self.readonly = readonly
91 self.required = required
93 self.help = args.get('help', '')
94 self.priority = priority
95 self.change_default = change_default
96 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
97 self.translate = translate
99 self._context = context
105 self.selectable = True
106 self.group_operator = args.get('group_operator', False)
109 setattr(self, a, args[a])
114 def set(self, cr, obj, id, name, value, user=None, context=None):
115 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
117 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
118 raise Exception(_('undefined get method !'))
120 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
121 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
122 res = obj.read(cr, uid, ids, [name], context=context)
123 return [x[name] for x in res]
126 # ---------------------------------------------------------
128 # ---------------------------------------------------------
129 class boolean(_column):
132 _symbol_f = lambda x: x and 'True' or 'False'
133 _symbol_set = (_symbol_c, _symbol_f)
135 def __init__(self, string='unknown', required=False, **args):
136 super(boolean, self).__init__(string=string, required=required, **args)
138 warnings.warn("Making a boolean field `required` has no effect, as NULL values are "
139 "automatically turned into False", PendingDeprecationWarning, stacklevel=2)
141 class integer(_column):
144 _symbol_f = lambda x: int(x or 0)
145 _symbol_set = (_symbol_c, _symbol_f)
146 _symbol_get = lambda self,x: x or 0
148 def __init__(self, string='unknown', required=False, **args):
149 super(integer, self).__init__(string=string, required=required, **args)
151 warnings.warn("Making an integer field `required` has no effect, as NULL values are "
152 "automatically turned into 0", PendingDeprecationWarning, stacklevel=2)
154 class integer_big(_column):
155 """Experimental 64 bit integer column type, currently unused.
157 TODO: this field should work fine for values up
158 to 32 bits, but greater values will not fit
159 in the XML-RPC int type, so a specific
160 get() method is needed to pass them as floats,
161 like what we do for integer functional fields.
163 _type = 'integer_big'
164 # do not reference the _symbol_* of integer class, as that would possibly
165 # unbind the lambda functions
167 _symbol_f = lambda x: int(x or 0)
168 _symbol_set = (_symbol_c, _symbol_f)
169 _symbol_get = lambda self,x: x or 0
171 def __init__(self, string='unknown', required=False, **args):
172 super(integer_big, self).__init__(string=string, required=required, **args)
174 warnings.warn("Making an integer_big field `required` has no effect, as NULL values are "
175 "automatically turned into 0", PendingDeprecationWarning, stacklevel=2)
177 class reference(_column):
179 _classic_read = False # post-process to handle missing target
181 def __init__(self, string, selection, size, **args):
182 _column.__init__(self, string=string, size=size, selection=selection, **args)
184 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
186 # copy initial values fetched previously.
188 result[value['id']] = value[name]
190 model, res_id = value[name].split(',')
191 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
192 result[value['id']] = False
198 def __init__(self, string, size, **args):
199 _column.__init__(self, string=string, size=size, **args)
200 self._symbol_set = (self._symbol_c, self._symbol_set_char)
202 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
203 def _symbol_set_char(self, symb):
205 # * we need to remove the "symb==False" from the next line BUT
206 # for now too many things rely on this broken behavior
207 # * the symb==None test should be common to all data types
208 if symb == None or symb == False:
211 # we need to convert the string to a unicode object to be able
212 # to evaluate its length (and possibly truncate it) reliably
213 u_symb = tools.ustr(symb)
215 return u_symb[:self.size].encode('utf8')
223 class float(_column):
226 _symbol_f = lambda x: __builtin__.float(x or 0.0)
227 _symbol_set = (_symbol_c, _symbol_f)
228 _symbol_get = lambda self,x: x or 0.0
230 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
231 _column.__init__(self, string=string, required=required, **args)
233 self.digits_compute = digits_compute
235 warnings.warn("Making a float field `required` has no effect, as NULL values are "
236 "automatically turned into 0.0", PendingDeprecationWarning, stacklevel=2)
239 def digits_change(self, cr):
240 if self.digits_compute:
241 t = self.digits_compute(cr)
242 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
249 """ Returns the current date in a format fit for being a
250 default value to a ``date`` field.
252 This method should be provided as is to the _defaults dict, it
253 should not be called.
255 return DT.date.today().strftime(
256 tools.DEFAULT_SERVER_DATE_FORMAT)
258 class datetime(_column):
262 """ Returns the current datetime in a format fit for being a
263 default value to a ``datetime`` field.
265 This method should be provided as is to the _defaults dict, it
266 should not be called.
268 return DT.datetime.now().strftime(
269 tools.DEFAULT_SERVER_DATETIME_FORMAT)
275 """ Returns the current time in a format fit for being a
276 default value to a ``time`` field.
278 This method should be proivided as is to the _defaults dict,
279 it should not be called.
281 return DT.datetime.now().strftime(
282 tools.DEFAULT_SERVER_TIME_FORMAT)
284 class binary(_column):
287 _symbol_f = lambda symb: symb and Binary(symb) or None
288 _symbol_set = (_symbol_c, _symbol_f)
289 _symbol_get = lambda self, x: x and str(x)
291 _classic_read = False
294 def __init__(self, string='unknown', filters=None, **args):
295 _column.__init__(self, string=string, **args)
296 self.filters = filters
298 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
311 # If client is requesting only the size of the field, we return it instead
312 # of the content. Presumably a separate request will be done to read the actual
313 # content if it's needed at some point.
314 # TODO: after 6.0 we should consider returning a dict with size and content instead of
315 # having an implicit convention for the value
316 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
317 res[i] = tools.human_size(long(val))
322 class selection(_column):
325 def __init__(self, selection, string='unknown', **args):
326 _column.__init__(self, string=string, **args)
327 self.selection = selection
329 # ---------------------------------------------------------
331 # ---------------------------------------------------------
334 # Values: (0, 0, { fields }) create
335 # (1, ID, { fields }) update
336 # (2, ID) remove (delete)
337 # (3, ID) unlink one (target id or target of relation)
339 # (5) unlink all (only valid for one2many)
341 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
342 class one2one(_column):
343 _classic_read = False
344 _classic_write = True
347 def __init__(self, obj, string='unknown', **args):
348 warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
349 _column.__init__(self, string=string, **args)
352 def set(self, cr, obj_src, id, field, act, user=None, context=None):
355 obj = obj_src.pool.get(self._obj)
356 self._table = obj_src.pool.get(self._obj)._table
358 id_new = obj.create(cr, user, act[1])
359 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
361 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
362 id = cr.fetchone()[0]
363 obj.write(cr, user, [id], act[1], context=context)
365 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
366 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
369 class many2one(_column):
370 _classic_read = False
371 _classic_write = True
374 _symbol_f = lambda x: x or None
375 _symbol_set = (_symbol_c, _symbol_f)
377 def __init__(self, obj, string='unknown', **args):
378 _column.__init__(self, string=string, **args)
381 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
389 res[r['id']] = r[name]
391 res.setdefault(id, '')
392 obj = obj.pool.get(self._obj)
394 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
395 # we use uid=1 because the visibility of a many2one field value (just id and name)
396 # must be the access right of the parent form and not the linked object itself.
397 records = dict(obj.name_get(cr, 1,
398 list(set([x for x in res.values() if isinstance(x, (int,long))])),
401 if res[id] in records:
402 res[id] = (res[id], records[res[id]])
407 def set(self, cr, obj_src, id, field, values, user=None, context=None):
410 obj = obj_src.pool.get(self._obj)
411 self._table = obj_src.pool.get(self._obj)._table
412 if type(values) == type([]):
415 id_new = obj.create(cr, act[2])
416 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
418 obj.write(cr, [act[1]], act[2], context=context)
420 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
421 elif act[0] == 3 or act[0] == 5:
422 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
424 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
427 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
429 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
431 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
432 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
435 class one2many(_column):
436 _classic_read = False
437 _classic_write = False
441 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
442 _column.__init__(self, string=string, **args)
444 self._fields_id = fields_id
446 #one2many can't be used as condition for defaults
447 assert(self.change_default != True)
449 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
453 context = context.copy()
454 context.update(self._context)
462 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
463 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
464 if r[self._fields_id] in res:
465 res[r[self._fields_id]].append(r['id'])
468 def set(self, cr, obj, id, field, values, user=None, context=None):
473 context = context.copy()
474 context.update(self._context)
475 context['no_store_function'] = True
478 _table = obj.pool.get(self._obj)._table
479 obj = obj.pool.get(self._obj)
482 act[2][self._fields_id] = id
483 id_new = obj.create(cr, user, act[2], context=context)
484 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
486 obj.write(cr, user, [act[1]], act[2], context=context)
488 obj.unlink(cr, user, [act[1]], context=context)
490 reverse_rel = obj._all_columns.get(self._fields_id)
491 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
492 # if the model has on delete cascade, just delete the row
493 if reverse_rel.column.ondelete == "cascade":
494 obj.unlink(cr, user, [act[1]], context=context)
496 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
498 # Must use write() to recompute parent_store structure if needed
499 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
501 reverse_rel = obj._all_columns.get(self._fields_id)
502 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
503 # if the model has on delete cascade, just delete the rows
504 if reverse_rel.column.ondelete == "cascade":
505 obj.unlink(cr, user, obj.search(cr, user, [(self._fields_id,'=',id)], context=context), context=context)
507 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
509 # Must use write() to recompute parent_store structure if needed
510 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
512 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
513 ids3 = map(lambda x:x[0], cr.fetchall())
514 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
517 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
518 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
522 # Values: (0, 0, { fields }) create
523 # (1, ID, { fields }) update (write fields to ID)
524 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
525 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
526 # (4, ID) link (add a relationship)
528 # (6, ?, ids) set a list of links
530 class many2many(_column):
531 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
532 low-level details of the intermediary relationship table transparently.
533 A many-to-many relationship is always symmetrical, and can be declared and accessed
534 from either endpoint model.
535 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
536 or id2 (destination foreign key column name) are not specified, the system will
537 provide default values. This will by default only allow one single symmetrical
538 many-to-many relationship between the source and destination model.
539 For multiple many-to-many relationship between the same models and for
540 relationships where source and destination models are the same, ``rel``, ``id1``
541 and ``id2`` should be specified explicitly.
543 :param str obj: destination model
544 :param str rel: optional name of the intermediary relationship table. If not specified,
545 a canonical name will be derived based on the alphabetically-ordered
546 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
547 Automatic naming is not possible when the source and destination are
548 the same, for obvious ambiguity reasons.
549 :param str id1: optional name for the column holding the foreign key to the current
550 model in the relationship table. If not specified, a canonical name
551 will be derived based on the model name (in the form: `src_model_id`).
552 :param str id2: optional name for the column holding the foreign key to the destination
553 model in the relationship table. If not specified, a canonical name
554 will be derived based on the model name (in the form: `dest_model_id`)
555 :param str string: field label
557 _classic_read = False
558 _classic_write = False
562 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
565 _column.__init__(self, string=string, **args)
567 if rel and '.' in rel:
568 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
569 'You used %s, which is not a valid SQL table name.')% (string,rel))
575 def _sql_names(self, source_model):
576 """Return the SQL names defining the structure of the m2m relationship table
578 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
579 local_col is the name of the column holding the current model's FK, and
580 dest_col is the name of the column holding the destination model's FK, and
582 tbl, col1, col2 = self._rel, self._id1, self._id2
583 if not all((tbl, col1, col2)):
584 # the default table name is based on the stable alphabetical order of tables
585 dest_model = source_model.pool.get(self._obj)
586 tables = tuple(sorted([source_model._table, dest_model._table]))
588 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
589 'is not possible when source and destination models are '\
591 tbl = '%s_%s_rel' % tables
593 col1 = '%s_id' % source_model._table
595 col2 = '%s_id' % dest_model._table
596 return (tbl, col1, col2)
598 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
609 warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
610 DeprecationWarning, stacklevel=2)
611 obj = model.pool.get(self._obj)
612 rel, id1, id2 = self._sql_names(model)
614 # static domains are lists, and are evaluated both here and on client-side, while string
615 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
616 # FIXME: make this distinction explicit in API!
617 domain = isinstance(self._domain, list) and self._domain or []
619 wquery = obj._where_calc(cr, user, domain, context=context)
620 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
621 from_c, where_c, where_params = wquery.get_sql()
623 where_c = ' AND ' + where_c
625 if offset or self._limit:
626 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
631 if self._limit is not None:
632 limit_str = ' LIMIT %d' % self._limit
634 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
635 FROM %(rel)s, %(from_c)s \
636 WHERE %(rel)s.%(id1)s IN %%s \
637 AND %(rel)s.%(id2)s = %(tbl)s.id \
649 'order_by': order_by,
652 cr.execute(query, [tuple(ids),] + where_params)
653 for r in cr.fetchall():
654 res[r[1]].append(r[0])
657 def set(self, cr, model, id, name, values, user=None, context=None):
662 rel, id1, id2 = self._sql_names(model)
663 obj = model.pool.get(self._obj)
665 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
668 idnew = obj.create(cr, user, act[2], context=context)
669 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
671 obj.write(cr, user, [act[1]], act[2], context=context)
673 obj.unlink(cr, user, [act[1]], context=context)
675 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
677 # following queries are in the same transaction - so should be relatively safe
678 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
679 if not cr.fetchone():
680 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
682 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
685 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
687 d1 = ' and ' + ' and '.join(d1)
690 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)
692 for act_nbr in act[2]:
693 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
696 # TODO: use a name_search
698 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
699 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
702 def get_nice_size(value):
704 if isinstance(value, (int,long)):
706 elif value: # this is supposed to be a string
708 return tools.human_size(size)
710 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
711 # and http://bugs.python.org/issue10066
712 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
714 def sanitize_binary_value(value):
715 # binary fields should be 7-bit ASCII base64-encoded data,
716 # but we do additional sanity checks to make sure the values
717 # are not something else that won't pass via XML-RPC
718 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
719 # these builtin types are meant to pass untouched
722 # Handle invalid bytes values that will cause problems
723 # for XML-RPC. See for more info:
724 # - http://bugs.python.org/issue10066
725 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
727 # Coercing to unicode would normally allow it to properly pass via
728 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
729 # (this works for _any_ byte values, thanks to the fallback
730 # to latin-1 passthrough encoding when decoding to unicode)
731 value = tools.ustr(value)
733 # Due to Python bug #10066 this could still yield invalid XML
734 # bytes, specifically in the low byte range, that will crash
735 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
736 # So check for low bytes values, and if any, perform
737 # base64 encoding - not very smart or useful, but this is
738 # our last resort to avoid crashing the request.
739 if invalid_xml_low_bytes.search(value):
740 # b64-encode after restoring the pure bytes with latin-1
741 # passthrough encoding
742 value = base64.b64encode(value.encode('latin-1'))
747 # ---------------------------------------------------------
749 # ---------------------------------------------------------
750 class function(_column):
752 A field whose value is computed by a function (rather
753 than being read from the database).
755 :param fnct: the callable that will compute the field value.
756 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
757 :param fnct_inv: the callable that will allow writing values in that field
758 (if not provided, the field is read-only).
759 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
761 :param str type: type of the field simulated by the function field
762 :param fnct_search: the callable that allows searching on the field
763 (if not provided, search will not return any result).
764 :param store: store computed value in database
765 (see :ref:`The *store* parameter <field-function-store>`).
766 :type store: True or dict specifying triggers for field computation
767 :param multi: name of batch for batch computation of function fields.
768 All fields with the same batch name will be computed by
769 a single function call. This changes the signature of the
772 .. _field-function-fnct: The ``fnct`` parameter
774 .. rubric:: The ``fnct`` parameter
776 The callable implementing the function field must have the following signature:
778 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
780 Implements the function field.
782 :param orm model: model to which the field belongs (should be ``self`` for
784 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
785 list of field names to compute.
786 :type field_name(s): str | [str]
787 :param arg: arbitrary value passed when declaring the function field
789 :return: mapping of ``ids`` to computed values, or if multi is provided,
790 to a map of field_names to computed values
792 The values in the returned dictionary must be of the type specified by the type
793 argument in the field declaration.
795 Here is an example with a simple function ``char`` function field::
798 def compute(self, cr, uid, ids, field_name, arg, context):
802 _columns['my_char'] = fields.function(compute, type='char', size=50)
804 # when called with ``ids=[1,2,3]``, ``compute`` could return:
808 3: False # null values should be returned explicitly too
811 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
812 of the field names that should be computed. Each value in the returned
813 dictionary must then be a dictionary mapping field names to values.
815 Here is an example where two function fields (``name`` and ``age``)
816 are both computed by a single function field::
819 def compute(self, cr, uid, ids, field_names, arg, context):
823 _columns['name'] = fields.function(compute_person_data, type='char',\
824 size=50, multi='person_data')
825 _columns[''age'] = fields.function(compute_person_data, type='integer',\
828 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
830 1: {'name': 'Bob', 'age': 23},
831 2: {'name': 'Sally', 'age': 19},
832 3: {'name': 'unknown', 'age': False}
835 .. _field-function-fnct-inv:
837 .. rubric:: The ``fnct_inv`` parameter
839 This callable implements the write operation for the function field
840 and must have the following signature:
842 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
844 Callable that implements the ``write`` operation for the function field.
846 :param orm model: model to which the field belongs (should be ``self`` for
848 :param int id: the identifier of the object to write on
849 :param str field_name: name of the field to set
850 :param fnct_inv_arg: arbitrary value passed when declaring the function field
853 When writing values for a function field, the ``multi`` parameter is ignored.
855 .. _field-function-fnct-search:
857 .. rubric:: The ``fnct_search`` parameter
859 This callable implements the search operation for the function field
860 and must have the following signature:
862 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
864 Callable that implements the ``search`` operation for the function field by expanding
865 a search criterion based on the function field into a new domain based only on
866 columns that are stored in the database.
868 :param orm model: model to which the field belongs (should be ``self`` for
870 :param orm model_again: same value as ``model`` (seriously! this is for backwards
872 :param str field_name: name of the field to search on
873 :param list criterion: domain component specifying the search criterion on the field.
875 :return: domain to use instead of ``criterion`` when performing the search.
876 This new domain must be based only on columns stored in the database, as it
877 will be used directly without any translation.
879 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
880 The most generic way to implement ``fnct_search`` is to directly search for the records that
881 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
882 ``[('id','in',[1,3,5])]``.
884 .. _field-function-store:
886 .. rubric:: The ``store`` parameter
888 The ``store`` parameter allows caching the result of the field computation in the
889 database, and defining the triggers that will invalidate that cache and force a
890 recomputation of the function field.
891 When not provided, the field is computed every time its value is read.
892 The value of ``store`` may be either ``True`` (to recompute the field value whenever
893 any field in the same record is modified), or a dictionary specifying a more
894 flexible set of recomputation triggers.
896 A trigger specification is a dictionary that maps the names of the models that
897 will trigger the computation, to a tuple describing the trigger rule, in the
901 'trigger_model': (mapping_function,
902 ['trigger_field1', 'trigger_field2'],
906 A trigger rule is defined by a 3-item tuple where:
908 * The ``mapping_function`` is defined as follows:
910 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
912 Callable that maps record ids of a trigger model to ids of the
913 corresponding records in the source model (whose field values
914 need to be recomputed).
916 :param orm model: trigger_model
917 :param list trigger_ids: ids of the records of trigger_model that were
920 :return: list of ids of the source model whose function field values
921 need to be recomputed
923 * The second item is a list of the fields who should act as triggers for
924 the computation. If an empty list is given, all fields will act as triggers.
925 * The last item is the priority, used to order the triggers when processing them
926 after any write operation on a model that has function field triggers. The
927 default priority is 10.
929 In fact, setting store = True is the same as using the following trigger dict::
932 'model_itself': (lambda self, cr, uid, ids, context: ids,
938 _classic_read = False
939 _classic_write = False
945 # multi: compute several fields in one call
947 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):
948 _column.__init__(self, **args)
951 self._fnct_inv = fnct_inv
954 if 'relation' in args:
955 self._obj = args['relation']
957 self.digits = args.get('digits', (16,2))
958 self.digits_compute = args.get('digits_compute', None)
960 self._fnct_inv_arg = fnct_inv_arg
964 self._fnct_search = fnct_search
967 if not fnct_search and not store:
968 self.selectable = False
971 if self._type != 'many2one':
972 # m2o fields need to return tuples with name_get, not just foreign keys
973 self._classic_read = True
974 self._classic_write = True
976 self._symbol_get=lambda x:x and str(x)
979 self._symbol_c = float._symbol_c
980 self._symbol_f = float._symbol_f
981 self._symbol_set = float._symbol_set
983 if type == 'boolean':
984 self._symbol_c = boolean._symbol_c
985 self._symbol_f = boolean._symbol_f
986 self._symbol_set = boolean._symbol_set
988 if type in ['integer','integer_big']:
989 self._symbol_c = integer._symbol_c
990 self._symbol_f = integer._symbol_f
991 self._symbol_set = integer._symbol_set
993 def digits_change(self, cr):
994 if self.digits_compute:
995 t = self.digits_compute(cr)
996 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
1000 def search(self, cr, uid, obj, name, args, context=None):
1001 if not self._fnct_search:
1002 #CHECKME: should raise an exception
1004 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1006 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1010 field_type = obj._columns[field]._type
1011 if field_type == "many2one":
1012 # make the result a tuple if it is not already one
1013 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1014 obj_model = obj.pool.get(obj._columns[field].relation)
1015 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1016 result = (value, dict_names[value])
1018 if field_type == 'binary':
1019 if context.get('bin_size', False):
1020 # client requests only the size of binary fields
1021 result = get_nice_size(value)
1023 result = sanitize_binary_value(value)
1025 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1026 # integer/long values greater than 2^31-1 are not supported
1027 # in pure XMLRPC, so we have to pass them as floats :-(
1028 # This is not needed for stored fields and non-functional integer
1029 # fields, as their values are constrained by the database backend
1030 # to the same 32bits signed int limit.
1031 result = float(value)
1034 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1035 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1037 if self._multi and id in result:
1038 for field, value in result[id].iteritems():
1040 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1041 elif result.get(id):
1042 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1045 def set(self, cr, obj, id, name, value, user=None, context=None):
1049 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1051 # ---------------------------------------------------------
1053 # ---------------------------------------------------------
1055 class related(function):
1056 """Field that points to some data inside another field of the current record.
1061 'foo_id': fields.many2one('my.foo', 'Foo'),
1062 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1066 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1067 self._field_get2(cr, uid, obj, context)
1068 i = len(self._arg)-1
1071 if type(sarg) in [type([]), type( (1,) )]:
1072 where = [(self._arg[i], 'in', sarg)]
1074 where = [(self._arg[i], '=', sarg)]
1076 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1078 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1080 return [(self._arg[0], 'in', sarg)]
1082 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1083 self._field_get2(cr, uid, obj, context=context)
1084 if type(ids) != type([]):
1086 objlst = obj.browse(cr, uid, ids)
1090 for i in range(len(self.arg)):
1091 if not t_data: break
1092 field_detail = self._relations[i]
1093 if not t_data[self.arg[i]]:
1094 if self._type not in ('one2many', 'many2many'):
1097 elif field_detail['type'] in ('one2many', 'many2many'):
1098 if self._type != "many2one":
1100 t_data = t_data[self.arg[i]][0]
1105 t_data = t_data[self.arg[i]]
1107 model = obj.pool.get(self._relations[-1]['object'])
1108 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1110 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1111 self._field_get2(cr, uid, obj, context)
1112 if not ids: return {}
1113 relation = obj._name
1114 if self._type in ('one2many', 'many2many'):
1115 res = dict([(i, []) for i in ids])
1117 res = {}.fromkeys(ids, False)
1119 objlst = obj.browse(cr, 1, ids, context=context)
1124 relation = obj._name
1125 for i in range(len(self.arg)):
1126 field_detail = self._relations[i]
1127 relation = field_detail['object']
1129 if not t_data[self.arg[i]]:
1135 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1136 t_data = t_data[self.arg[i]][0]
1138 t_data = t_data[self.arg[i]]
1139 if type(t_data) == type(objlst[0]):
1140 res[data.id] = t_data.id
1142 res[data.id] = t_data
1143 if self._type=='many2one':
1144 ids = filter(None, res.values())
1146 # name_get as root, as seeing the name of a related
1147 # object depends on access right of source document,
1148 # not target, so user may not have access.
1149 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1152 res[r] = (res[r], ng[res[r]])
1153 elif self._type in ('one2many', 'many2many'):
1156 res[r] = [x.id for x in res[r]]
1159 def __init__(self, *arg, **args):
1161 self._relations = []
1162 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1163 if self.store is True:
1164 # TODO: improve here to change self.store = {...} according to related objects
1167 def _field_get2(self, cr, uid, obj, context=None):
1170 obj_name = obj._name
1171 for i in range(len(self._arg)):
1172 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1173 self._relations.append({
1178 if f.get('relation',False):
1179 obj_name = f['relation']
1180 self._relations[-1]['relation'] = f['relation']
1183 class sparse(function):
1185 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1187 + For a many2many field, a list of tuples is expected.
1188 Here is the list of tuple that are accepted, with the corresponding semantics ::
1190 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1191 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1192 (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)
1193 (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)
1194 (4, ID) link to existing record with id = ID (adds a relationship)
1195 (5) unlink all (like using (3,ID) for all linked records)
1196 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1199 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1201 + For a one2many field, a lits of tuples is expected.
1202 Here is the list of tuple that are accepted, with the corresponding semantics ::
1204 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1205 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1206 (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)
1209 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1212 if self._type == 'many2many':
1213 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1216 elif self._type == 'one2many':
1219 relation_obj = obj.pool.get(self.relation)
1221 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1223 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1225 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1227 relation_obj.unlink(cr, uid, vals[1], context=context)
1228 read_value.remove(vals[1])
1233 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1234 if not type(ids) == list:
1236 records = obj.browse(cr, uid, ids, context=context)
1237 for record in records:
1238 # grab serialized value as object - already deserialized
1239 serialized = getattr(record, self.serialization_field)
1241 # simply delete the key to unset it.
1242 serialized.pop(field_name, None)
1244 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1245 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1248 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1250 records = obj.browse(cr, uid, ids, context=context)
1251 for record in records:
1252 # grab serialized value as object - already deserialized
1253 serialized = getattr(record, self.serialization_field)
1254 results[record.id] = {}
1255 for field_name in field_names:
1256 if obj._columns[field_name]._type in ['one2many']:
1257 value = serialized.get(field_name, [])
1259 results[record.id].update(field_name=value)
1262 def __init__(self, serialization_field, **kwargs):
1263 self.serialization_field = serialization_field
1264 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', method=True, **kwargs)
1270 # ---------------------------------------------------------
1272 # ---------------------------------------------------------
1274 class dummy(function):
1275 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1278 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1281 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1284 def __init__(self, *arg, **args):
1286 self._relations = []
1287 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1289 # ---------------------------------------------------------
1291 # ---------------------------------------------------------
1293 class serialized(_column):
1294 """ A field able to store an arbitrary python data structure.
1296 Note: only plain components allowed.
1299 def _symbol_set_struct(val):
1300 return json.dumps(val)
1302 def _symbol_get_struct(self, val):
1303 return json.loads(val or '{}')
1306 _type = 'serialized'
1309 _symbol_f = _symbol_set_struct
1310 _symbol_set = (_symbol_c, _symbol_f)
1311 _symbol_get = _symbol_get_struct
1313 # TODO: review completly this class for speed improvement
1314 class property(function):
1316 def _get_default(self, obj, cr, uid, prop_name, context=None):
1317 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1319 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1320 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1322 :param list of string prop_names: list of name of property fields for those we want the default value
1323 :return: map of property field names to their default value
1326 prop = obj.pool.get('ir.property')
1328 for prop_name in prop_names:
1329 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1332 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1333 prop = obj.pool.get('ir.property')
1334 vids = [obj._name + ',' + str(oid) for oid in ids]
1336 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1337 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1339 domain = [('res_id', 'in', vids)] + domain
1340 return prop.search(cr, uid, domain, context=context)
1342 # TODO: to rewrite more clean
1343 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1347 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1349 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1351 default_val = self._get_default(obj, cr, uid, prop_name, context)
1353 property_create = False
1354 if isinstance(default_val, openerp.osv.orm.browse_record):
1355 if default_val.id != id_val:
1356 property_create = True
1357 elif default_val != id_val:
1358 property_create = True
1361 def_id = self._field_get(cr, uid, obj._name, prop_name)
1362 company = obj.pool.get('res.company')
1363 cid = company._company_default_get(cr, uid, obj._name, def_id,
1365 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1367 prop = obj.pool.get('ir.property')
1368 return prop.create(cr, uid, {
1369 'name': propdef.name,
1371 'res_id': obj._name+','+str(id),
1373 'fields_id': def_id,
1378 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1379 prop = obj.pool.get('ir.property')
1380 # get the default values (for res_id = False) for the property fields
1381 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1383 # build the dictionary that will be returned
1386 res[id] = default_val.copy()
1388 for prop_name in prop_names:
1389 property_field = obj._all_columns.get(prop_name).column
1390 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1391 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1392 # in order to make a name_get in batch for all the ids needed.
1395 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1396 obj_reference = obj._name + ',' + str(id)
1397 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1399 res[id][prop_name] = value
1400 # Check existence as root (as seeing the name of a related
1401 # object depends on access right of source document,
1402 # not target, so user may not have access) in order to avoid
1403 # pointing on an unexisting record.
1404 if property_destination_obj:
1405 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1406 name_get_ids[id] = res[id][prop_name].id
1408 res[id][prop_name] = False
1409 if property_destination_obj:
1410 # name_get as root (as seeing the name of a related
1411 # object depends on access right of source document,
1412 # not target, so user may not have access.)
1413 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1414 # the property field is a m2o, we need to return a tuple with (id, name)
1415 for k, v in name_get_ids.iteritems():
1416 if res[k][prop_name]:
1417 res[k][prop_name] = (v , name_get_values.get(v))
1420 def _field_get(self, cr, uid, model_name, prop):
1421 if not self.field_id.get(cr.dbname):
1422 cr.execute('SELECT id \
1423 FROM ir_model_fields \
1424 WHERE name=%s AND model=%s', (prop, model_name))
1426 self.field_id[cr.dbname] = res and res[0]
1427 return self.field_id[cr.dbname]
1429 def __init__(self, obj_prop, **args):
1430 # TODO remove obj_prop parameter (use many2one type)
1432 function.__init__(self, self._fnct_read, False, self._fnct_write,
1433 obj_prop, multi='properties', **args)
1439 def field_to_dict(model, cr, user, field, context=None):
1440 """ Return a dictionary representation of a field.
1442 The string, help, and selection attributes (if any) are untranslated. This
1443 representation is the one returned by fields_get() (fields_get() will do
1448 res = {'type': field._type}
1449 # This additional attributes for M2M and function field is added
1450 # because we need to display tooltip with this additional information
1451 # when client is started in debug mode.
1452 if isinstance(field, function):
1453 res['function'] = field._fnct and field._fnct.func_name or False
1454 res['store'] = field.store
1455 if isinstance(field.store, dict):
1456 res['store'] = str(field.store)
1457 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1458 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1459 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1460 res['func_obj'] = field._obj or False
1461 if isinstance(field, many2many):
1462 (table, col1, col2) = field._sql_names(model)
1463 res['related_columns'] = [col1, col2]
1464 res['third_table'] = table
1465 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1466 'change_default', 'translate', 'help', 'select', 'selectable'):
1467 if getattr(field, arg):
1468 res[arg] = getattr(field, arg)
1469 for arg in ('digits', 'invisible', 'filters'):
1470 if getattr(field, arg, None):
1471 res[arg] = getattr(field, arg)
1474 res['string'] = field.string
1476 res['help'] = field.help
1478 if hasattr(field, 'selection'):
1479 if isinstance(field.selection, (tuple, list)):
1480 res['selection'] = field.selection
1482 # call the 'dynamic selection' function
1483 res['selection'] = field.selection(model, cr, user, context)
1484 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1485 res['relation'] = field._obj
1486 res['domain'] = field._domain
1487 res['context'] = field._context
1489 if isinstance(field, one2many):
1490 res['relation_field'] = field._fields_id
1495 class column_info(object):
1496 """Struct containing details about an osv column, either one local to
1497 its model, or one inherited via _inherits.
1499 :attr name: name of the column
1500 :attr column: column instance, subclass of osv.fields._column
1501 :attr parent_model: if the column is inherited, name of the model
1502 that contains it, None for local columns.
1503 :attr parent_column: the name of the column containing the m2o
1504 relationship to the parent model that contains
1505 this column, None for local columns.
1506 :attr original_parent: if the column is inherited, name of the original
1507 parent model that contains it i.e in case of multilevel
1508 inheritence, None for local columns.
1510 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1512 self.column = column
1513 self.parent_model = parent_model
1514 self.parent_column = parent_column
1515 self.original_parent = original_parent
1517 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: