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
44 import openerp.netsvc as netsvc
45 import openerp.tools as tools
46 from openerp.tools.translate import _
48 def _symbol_set(symb):
49 if symb == None or symb == False:
51 elif isinstance(symb, unicode):
52 return symb.encode('utf-8')
56 class _column(object):
57 """ Base of all fields, a database column
59 An instance of this object is a *description* of a database column. It will
60 not hold any data, but only provide the methods to manipulate data of an
61 ORM record or even prepare/update the database to hold such a field of data.
71 _symbol_f = _symbol_set
72 _symbol_set = (_symbol_c, _symbol_f)
75 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):
78 The 'manual' keyword argument specifies if the field is a custom one.
79 It corresponds to the 'state' column in ir_model_fields.
86 self.states = states or {}
88 self.readonly = readonly
89 self.required = required
91 self.help = args.get('help', '')
92 self.priority = priority
93 self.change_default = change_default
94 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
95 self.translate = translate
97 self._context = context
103 self.selectable = True
104 self.group_operator = args.get('group_operator', False)
107 setattr(self, a, args[a])
112 def set(self, cr, obj, id, name, value, user=None, context=None):
113 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
115 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
116 raise Exception(_('undefined get method !'))
118 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
119 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
120 res = obj.read(cr, uid, ids, [name], context=context)
121 return [x[name] for x in res]
124 # ---------------------------------------------------------
126 # ---------------------------------------------------------
127 class boolean(_column):
130 _symbol_f = lambda x: x and 'True' or 'False'
131 _symbol_set = (_symbol_c, _symbol_f)
133 class integer(_column):
136 _symbol_f = lambda x: int(x or 0)
137 _symbol_set = (_symbol_c, _symbol_f)
138 _symbol_get = lambda self,x: x or 0
140 class integer_big(_column):
141 """Experimental 64 bit integer column type, currently unused.
143 TODO: this field should work fine for values up
144 to 32 bits, but greater values will not fit
145 in the XML-RPC int type, so a specific
146 get() method is needed to pass them as floats,
147 like what we do for integer functional fields.
149 _type = 'integer_big'
150 # do not reference the _symbol_* of integer class, as that would possibly
151 # unbind the lambda functions
153 _symbol_f = lambda x: int(x or 0)
154 _symbol_set = (_symbol_c, _symbol_f)
155 _symbol_get = lambda self,x: x or 0
157 class reference(_column):
159 def __init__(self, string, selection, size, **args):
160 _column.__init__(self, string=string, size=size, selection=selection, **args)
166 def __init__(self, string, size, **args):
167 _column.__init__(self, string=string, size=size, **args)
168 self._symbol_set = (self._symbol_c, self._symbol_set_char)
170 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
171 def _symbol_set_char(self, symb):
173 # * we need to remove the "symb==False" from the next line BUT
174 # for now too many things rely on this broken behavior
175 # * the symb==None test should be common to all data types
176 if symb == None or symb == False:
179 # we need to convert the string to a unicode object to be able
180 # to evaluate its length (and possibly truncate it) reliably
181 u_symb = tools.ustr(symb)
183 return u_symb[:self.size].encode('utf8')
191 class float(_column):
194 _symbol_f = lambda x: __builtin__.float(x or 0.0)
195 _symbol_set = (_symbol_c, _symbol_f)
196 _symbol_get = lambda self,x: x or 0.0
198 def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
199 _column.__init__(self, string=string, **args)
201 self.digits_compute = digits_compute
204 def digits_change(self, cr):
205 if self.digits_compute:
206 t = self.digits_compute(cr)
207 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
214 """ Returns the current date in a format fit for being a
215 default value to a ``date`` field.
217 This method should be provided as is to the _defaults dict, it
218 should not be called.
220 return DT.date.today().strftime(
221 tools.DEFAULT_SERVER_DATE_FORMAT)
223 class datetime(_column):
227 """ Returns the current datetime in a format fit for being a
228 default value to a ``datetime`` field.
230 This method should be provided as is to the _defaults dict, it
231 should not be called.
233 return DT.datetime.now().strftime(
234 tools.DEFAULT_SERVER_DATETIME_FORMAT)
240 """ Returns the current time in a format fit for being a
241 default value to a ``time`` field.
243 This method should be proivided as is to the _defaults dict,
244 it should not be called.
246 return DT.datetime.now().strftime(
247 tools.DEFAULT_SERVER_TIME_FORMAT)
249 class binary(_column):
252 _symbol_f = lambda symb: symb and Binary(symb) or None
253 _symbol_set = (_symbol_c, _symbol_f)
254 _symbol_get = lambda self, x: x and str(x)
256 _classic_read = False
259 def __init__(self, string='unknown', filters=None, **args):
260 _column.__init__(self, string=string, **args)
261 self.filters = filters
263 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
276 # If client is requesting only the size of the field, we return it instead
277 # of the content. Presumably a separate request will be done to read the actual
278 # content if it's needed at some point.
279 # TODO: after 6.0 we should consider returning a dict with size and content instead of
280 # having an implicit convention for the value
281 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
282 res[i] = tools.human_size(long(val))
287 class selection(_column):
290 def __init__(self, selection, string='unknown', **args):
291 _column.__init__(self, string=string, **args)
292 self.selection = selection
294 # ---------------------------------------------------------
296 # ---------------------------------------------------------
299 # Values: (0, 0, { fields }) create
300 # (1, ID, { fields }) update
301 # (2, ID) remove (delete)
302 # (3, ID) unlink one (target id or target of relation)
304 # (5) unlink all (only valid for one2many)
306 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
307 class one2one(_column):
308 _classic_read = False
309 _classic_write = True
312 def __init__(self, obj, string='unknown', **args):
313 warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
314 _column.__init__(self, string=string, **args)
317 def set(self, cr, obj_src, id, field, act, user=None, context=None):
320 obj = obj_src.pool.get(self._obj)
321 self._table = obj_src.pool.get(self._obj)._table
323 id_new = obj.create(cr, user, act[1])
324 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
326 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
327 id = cr.fetchone()[0]
328 obj.write(cr, user, [id], act[1], context=context)
330 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
331 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
334 class many2one(_column):
335 _classic_read = False
336 _classic_write = True
339 _symbol_f = lambda x: x or None
340 _symbol_set = (_symbol_c, _symbol_f)
342 def __init__(self, obj, string='unknown', **args):
343 _column.__init__(self, string=string, **args)
346 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
354 res[r['id']] = r[name]
356 res.setdefault(id, '')
357 obj = obj.pool.get(self._obj)
359 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
360 # we use uid=1 because the visibility of a many2one field value (just id and name)
361 # must be the access right of the parent form and not the linked object itself.
362 records = dict(obj.name_get(cr, 1,
363 list(set([x for x in res.values() if isinstance(x, (int,long))])),
366 if res[id] in records:
367 res[id] = (res[id], records[res[id]])
372 def set(self, cr, obj_src, id, field, values, user=None, context=None):
375 obj = obj_src.pool.get(self._obj)
376 self._table = obj_src.pool.get(self._obj)._table
377 if type(values) == type([]):
380 id_new = obj.create(cr, act[2])
381 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
383 obj.write(cr, [act[1]], act[2], context=context)
385 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
386 elif act[0] == 3 or act[0] == 5:
387 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
389 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
392 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
394 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
396 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
397 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
400 class one2many(_column):
401 _classic_read = False
402 _classic_write = False
406 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
407 _column.__init__(self, string=string, **args)
409 self._fields_id = fields_id
411 #one2many can't be used as condition for defaults
412 assert(self.change_default != True)
414 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
418 context = context.copy()
419 context.update(self._context)
427 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
428 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
429 if r[self._fields_id] in res:
430 res[r[self._fields_id]].append(r['id'])
433 def set(self, cr, obj, id, field, values, user=None, context=None):
438 context = context.copy()
439 context.update(self._context)
440 context['no_store_function'] = True
443 _table = obj.pool.get(self._obj)._table
444 obj = obj.pool.get(self._obj)
447 act[2][self._fields_id] = id
448 id_new = obj.create(cr, user, act[2], context=context)
449 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
451 obj.write(cr, user, [act[1]], act[2], context=context)
453 obj.unlink(cr, user, [act[1]], context=context)
455 reverse_rel = obj._all_columns.get(self._fields_id)
456 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
457 # if the model has on delete cascade, just delete the row
458 if reverse_rel.column.ondelete == "cascade":
459 obj.unlink(cr, user, [act[1]], context=context)
461 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
463 # Must use write() to recompute parent_store structure if needed
464 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
466 reverse_rel = obj._all_columns.get(self._fields_id)
467 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
468 # if the model has on delete cascade, just delete the rows
469 if reverse_rel.column.ondelete == "cascade":
470 obj.unlink(cr, user, obj.search(cr, user, [(self._fields_id,'=',id)], context=context), context=context)
472 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
474 # Must use write() to recompute parent_store structure if needed
475 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
477 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
478 ids3 = map(lambda x:x[0], cr.fetchall())
479 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
482 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
483 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
487 # Values: (0, 0, { fields }) create
488 # (1, ID, { fields }) update (write fields to ID)
489 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
490 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
491 # (4, ID) link (add a relationship)
493 # (6, ?, ids) set a list of links
495 class many2many(_column):
496 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
497 low-level details of the intermediary relationship table transparently.
499 :param str obj: destination model
500 :param str rel: optional name of the intermediary relationship table. If not specified,
501 a canonical name will be derived based on the alphabetically-ordered
502 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
503 Automatic naming is not possible when the source and destination are
504 the same, for obvious ambiguity reasons.
505 :param str id1: optional name for the column holding the foreign key to the current
506 model in the relationship table. If not specified, a canonical name
507 will be derived based on the model name (in the form: `src_model_id`).
508 :param str id2: optional name for the column holding the foreign key to the destination
509 model in the relationship table. If not specified, a canonical name
510 will be derived based on the model name (in the form: `dest_model_id`)
511 :param str string: field label
513 _classic_read = False
514 _classic_write = False
518 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
521 _column.__init__(self, string=string, **args)
523 if rel and '.' in rel:
524 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
525 'You used %s, which is not a valid SQL table name.')% (string,rel))
531 def _sql_names(self, source_model):
532 """Return the SQL names defining the structure of the m2m relationship table
534 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
535 local_col is the name of the column holding the current model's FK, and
536 dest_col is the name of the column holding the destination model's FK, and
538 tbl, col1, col2 = self._rel, self._id1, self._id2
539 if not all((tbl, col1, col2)):
540 # the default table name is based on the stable alphabetical order of tables
541 dest_model = source_model.pool.get(self._obj)
542 tables = tuple(sorted([source_model._table, dest_model._table]))
544 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
545 'is not possible when source and destination models are '\
547 tbl = '%s_%s_rel' % tables
549 col1 = '%s_id' % source_model._table
551 col2 = '%s_id' % dest_model._table
552 return (tbl, col1, col2)
554 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
565 warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
566 DeprecationWarning, stacklevel=2)
567 obj = model.pool.get(self._obj)
568 rel, id1, id2 = self._sql_names(model)
570 # static domains are lists, and are evaluated both here and on client-side, while string
571 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
572 # FIXME: make this distinction explicit in API!
573 domain = isinstance(self._domain, list) and self._domain or []
575 wquery = obj._where_calc(cr, user, domain, context=context)
576 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
577 from_c, where_c, where_params = wquery.get_sql()
579 where_c = ' AND ' + where_c
581 if offset or self._limit:
582 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
587 if self._limit is not None:
588 limit_str = ' LIMIT %d' % self._limit
590 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
591 FROM %(rel)s, %(from_c)s \
592 WHERE %(rel)s.%(id1)s IN %%s \
593 AND %(rel)s.%(id2)s = %(tbl)s.id \
605 'order_by': order_by,
608 cr.execute(query, [tuple(ids),] + where_params)
609 for r in cr.fetchall():
610 res[r[1]].append(r[0])
613 def set(self, cr, model, id, name, values, user=None, context=None):
618 rel, id1, id2 = self._sql_names(model)
619 obj = model.pool.get(self._obj)
621 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
624 idnew = obj.create(cr, user, act[2], context=context)
625 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
627 obj.write(cr, user, [act[1]], act[2], context=context)
629 obj.unlink(cr, user, [act[1]], context=context)
631 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
633 # following queries are in the same transaction - so should be relatively safe
634 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
635 if not cr.fetchone():
636 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
638 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
641 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
643 d1 = ' and ' + ' and '.join(d1)
646 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)
648 for act_nbr in act[2]:
649 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
652 # TODO: use a name_search
654 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
655 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
658 def get_nice_size(value):
660 if isinstance(value, (int,long)):
662 elif value: # this is supposed to be a string
664 return tools.human_size(size)
666 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
667 # and http://bugs.python.org/issue10066
668 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
670 def sanitize_binary_value(value):
671 # binary fields should be 7-bit ASCII base64-encoded data,
672 # but we do additional sanity checks to make sure the values
673 # are not something else that won't pass via XML-RPC
674 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
675 # these builtin types are meant to pass untouched
678 # Handle invalid bytes values that will cause problems
679 # for XML-RPC. See for more info:
680 # - http://bugs.python.org/issue10066
681 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
683 # Coercing to unicode would normally allow it to properly pass via
684 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
685 # (this works for _any_ byte values, thanks to the fallback
686 # to latin-1 passthrough encoding when decoding to unicode)
687 value = tools.ustr(value)
689 # Due to Python bug #10066 this could still yield invalid XML
690 # bytes, specifically in the low byte range, that will crash
691 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
692 # So check for low bytes values, and if any, perform
693 # base64 encoding - not very smart or useful, but this is
694 # our last resort to avoid crashing the request.
695 if invalid_xml_low_bytes.search(value):
696 # b64-encode after restoring the pure bytes with latin-1
697 # passthrough encoding
698 value = base64.b64encode(value.encode('latin-1'))
703 # ---------------------------------------------------------
705 # ---------------------------------------------------------
706 class function(_column):
708 A field whose value is computed by a function (rather
709 than being read from the database).
711 :param fnct: the callable that will compute the field value.
712 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
713 :param fnct_inv: the callable that will allow writing values in that field
714 (if not provided, the field is read-only).
715 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
717 :param str type: type of the field simulated by the function field
718 :param fnct_search: the callable that allows searching on the field
719 (if not provided, search will not return any result).
720 :param store: store computed value in database
721 (see :ref:`The *store* parameter <field-function-store>`).
722 :type store: True or dict specifying triggers for field computation
723 :param multi: name of batch for batch computation of function fields.
724 All fields with the same batch name will be computed by
725 a single function call. This changes the signature of the
728 .. _field-function-fnct: The ``fnct`` parameter
730 .. rubric:: The ``fnct`` parameter
732 The callable implementing the function field must have the following signature:
734 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
736 Implements the function field.
738 :param orm model: model to which the field belongs (should be ``self`` for
740 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
741 list of field names to compute.
742 :type field_name(s): str | [str]
743 :param arg: arbitrary value passed when declaring the function field
745 :return: mapping of ``ids`` to computed values, or if multi is provided,
746 to a map of field_names to computed values
748 The values in the returned dictionary must be of the type specified by the type
749 argument in the field declaration.
751 Here is an example with a simple function ``char`` function field::
754 def compute(self, cr, uid, ids, field_name, arg, context):
758 _columns['my_char'] = fields.function(compute, type='char', size=50)
760 # when called with ``ids=[1,2,3]``, ``compute`` could return:
764 3: False # null values should be returned explicitly too
767 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
768 of the field names that should be computed. Each value in the returned
769 dictionary must then be a dictionary mapping field names to values.
771 Here is an example where two function fields (``name`` and ``age``)
772 are both computed by a single function field::
775 def compute(self, cr, uid, ids, field_names, arg, context):
779 _columns['name'] = fields.function(compute_person_data, type='char',\
780 size=50, multi='person_data')
781 _columns[''age'] = fields.function(compute_person_data, type='integer',\
784 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
786 1: {'name': 'Bob', 'age': 23},
787 2: {'name': 'Sally', 'age': 19},
788 3: {'name': 'unknown', 'age': False}
791 .. _field-function-fnct-inv:
793 .. rubric:: The ``fnct_inv`` parameter
795 This callable implements the write operation for the function field
796 and must have the following signature:
798 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
800 Callable that implements the ``write`` operation for the function field.
802 :param orm model: model to which the field belongs (should be ``self`` for
804 :param int id: the identifier of the object to write on
805 :param str field_name: name of the field to set
806 :param fnct_inv_arg: arbitrary value passed when declaring the function field
809 When writing values for a function field, the ``multi`` parameter is ignored.
811 .. _field-function-fnct-search:
813 .. rubric:: The ``fnct_search`` parameter
815 This callable implements the search operation for the function field
816 and must have the following signature:
818 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
820 Callable that implements the ``search`` operation for the function field by expanding
821 a search criterion based on the function field into a new domain based only on
822 columns that are stored in the database.
824 :param orm model: model to which the field belongs (should be ``self`` for
826 :param orm model_again: same value as ``model`` (seriously! this is for backwards
828 :param str field_name: name of the field to search on
829 :param list criterion: domain component specifying the search criterion on the field.
831 :return: domain to use instead of ``criterion`` when performing the search.
832 This new domain must be based only on columns stored in the database, as it
833 will be used directly without any translation.
835 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
836 The most generic way to implement ``fnct_search`` is to directly search for the records that
837 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
838 ``[('id','in',[1,3,5])]``.
840 .. _field-function-store:
842 .. rubric:: The ``store`` parameter
844 The ``store`` parameter allows caching the result of the field computation in the
845 database, and defining the triggers that will invalidate that cache and force a
846 recomputation of the function field.
847 When not provided, the field is computed every time its value is read.
848 The value of ``store`` may be either ``True`` (to recompute the field value whenever
849 any field in the same record is modified), or a dictionary specifying a more
850 flexible set of recomputation triggers.
852 A trigger specification is a dictionary that maps the names of the models that
853 will trigger the computation, to a tuple describing the trigger rule, in the
857 'trigger_model': (mapping_function,
858 ['trigger_field1', 'trigger_field2'],
862 A trigger rule is defined by a 3-item tuple where:
864 * The ``mapping_function`` is defined as follows:
866 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
868 Callable that maps record ids of a trigger model to ids of the
869 corresponding records in the source model (whose field values
870 need to be recomputed).
872 :param orm model: trigger_model
873 :param list trigger_ids: ids of the records of trigger_model that were
876 :return: list of ids of the source model whose function field values
877 need to be recomputed
879 * The second item is a list of the fields who should act as triggers for
880 the computation. If an empty list is given, all fields will act as triggers.
881 * The last item is the priority, used to order the triggers when processing them
882 after any write operation on a model that has function field triggers. The
883 default priority is 10.
885 In fact, setting store = True is the same as using the following trigger dict::
888 'model_itself': (lambda self, cr, uid, ids, context: ids,
894 _classic_read = False
895 _classic_write = False
901 # multi: compute several fields in one call
903 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):
904 _column.__init__(self, **args)
907 self._fnct_inv = fnct_inv
910 if 'relation' in args:
911 self._obj = args['relation']
913 self.digits = args.get('digits', (16,2))
914 self.digits_compute = args.get('digits_compute', None)
916 self._fnct_inv_arg = fnct_inv_arg
920 self._fnct_search = fnct_search
923 if not fnct_search and not store:
924 self.selectable = False
927 if self._type != 'many2one':
928 # m2o fields need to return tuples with name_get, not just foreign keys
929 self._classic_read = True
930 self._classic_write = True
932 self._symbol_get=lambda x:x and str(x)
935 self._symbol_c = float._symbol_c
936 self._symbol_f = float._symbol_f
937 self._symbol_set = float._symbol_set
939 if type == 'boolean':
940 self._symbol_c = boolean._symbol_c
941 self._symbol_f = boolean._symbol_f
942 self._symbol_set = boolean._symbol_set
944 if type in ['integer','integer_big']:
945 self._symbol_c = integer._symbol_c
946 self._symbol_f = integer._symbol_f
947 self._symbol_set = integer._symbol_set
949 def digits_change(self, cr):
950 if self.digits_compute:
951 t = self.digits_compute(cr)
952 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
956 def search(self, cr, uid, obj, name, args, context=None):
957 if not self._fnct_search:
958 #CHECKME: should raise an exception
960 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
962 def postprocess(self, cr, uid, obj, field, value=None, context=None):
966 field_type = obj._columns[field]._type
967 if field_type == "many2one":
968 # make the result a tuple if it is not already one
969 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
970 obj_model = obj.pool.get(obj._columns[field].relation)
971 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
972 result = (value, dict_names[value])
974 if field_type == 'binary':
975 if context.get('bin_size', False):
976 # client requests only the size of binary fields
977 result = get_nice_size(value)
979 result = sanitize_binary_value(value)
981 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
982 # integer/long values greater than 2^31-1 are not supported
983 # in pure XMLRPC, so we have to pass them as floats :-(
984 # This is not needed for stored fields and non-functional integer
985 # fields, as their values are constrained by the database backend
986 # to the same 32bits signed int limit.
987 result = float(value)
990 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
991 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
993 if self._multi and id in result:
994 for field, value in result[id].iteritems():
996 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
998 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1001 def set(self, cr, obj, id, name, value, user=None, context=None):
1005 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1007 # ---------------------------------------------------------
1009 # ---------------------------------------------------------
1011 class related(function):
1012 """Field that points to some data inside another field of the current record.
1017 'foo_id': fields.many2one('my.foo', 'Foo'),
1018 'bar': fields.related('frol', 'foo_id', type='char', string='Frol of Foo'),
1022 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1023 self._field_get2(cr, uid, obj, context)
1024 i = len(self._arg)-1
1027 if type(sarg) in [type([]), type( (1,) )]:
1028 where = [(self._arg[i], 'in', sarg)]
1030 where = [(self._arg[i], '=', sarg)]
1032 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1034 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1036 return [(self._arg[0], 'in', sarg)]
1038 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1039 self._field_get2(cr, uid, obj, context=context)
1040 if type(ids) != type([]):
1042 objlst = obj.browse(cr, uid, ids)
1046 for i in range(len(self.arg)):
1047 if not t_data: break
1048 field_detail = self._relations[i]
1049 if not t_data[self.arg[i]]:
1050 if self._type not in ('one2many', 'many2many'):
1053 elif field_detail['type'] in ('one2many', 'many2many'):
1054 if self._type != "many2one":
1056 t_data = t_data[self.arg[i]][0]
1061 t_data = t_data[self.arg[i]]
1063 model = obj.pool.get(self._relations[-1]['object'])
1064 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1066 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1067 self._field_get2(cr, uid, obj, context)
1068 if not ids: return {}
1069 relation = obj._name
1070 if self._type in ('one2many', 'many2many'):
1071 res = dict([(i, []) for i in ids])
1073 res = {}.fromkeys(ids, False)
1075 objlst = obj.browse(cr, 1, ids, context=context)
1080 relation = obj._name
1081 for i in range(len(self.arg)):
1082 field_detail = self._relations[i]
1083 relation = field_detail['object']
1085 if not t_data[self.arg[i]]:
1091 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1092 t_data = t_data[self.arg[i]][0]
1094 t_data = t_data[self.arg[i]]
1095 if type(t_data) == type(objlst[0]):
1096 res[data.id] = t_data.id
1098 res[data.id] = t_data
1099 if self._type=='many2one':
1100 ids = filter(None, res.values())
1102 # name_get as root, as seeing the name of a related
1103 # object depends on access right of source document,
1104 # not target, so user may not have access.
1105 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1108 res[r] = (res[r], ng[res[r]])
1109 elif self._type in ('one2many', 'many2many'):
1112 res[r] = [x.id for x in res[r]]
1115 def __init__(self, *arg, **args):
1117 self._relations = []
1118 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1119 if self.store is True:
1120 # TODO: improve here to change self.store = {...} according to related objects
1123 def _field_get2(self, cr, uid, obj, context=None):
1126 obj_name = obj._name
1127 for i in range(len(self._arg)):
1128 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1129 self._relations.append({
1134 if f.get('relation',False):
1135 obj_name = f['relation']
1136 self._relations[-1]['relation'] = f['relation']
1138 # ---------------------------------------------------------
1140 # ---------------------------------------------------------
1142 class dummy(function):
1143 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1146 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1149 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1152 def __init__(self, *arg, **args):
1154 self._relations = []
1155 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1157 # ---------------------------------------------------------
1159 # ---------------------------------------------------------
1160 class serialized(_column):
1161 def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
1162 self._serialize_func = serialize_func
1163 self._deserialize_func = deserialize_func
1165 self._symbol_set = (self._symbol_c, self._serialize_func)
1166 self._symbol_get = self._deserialize_func
1167 super(serialized, self).__init__(string=string, **args)
1169 # TODO: review completly this class for speed improvement
1170 class property(function):
1172 def _get_default(self, obj, cr, uid, prop_name, context=None):
1173 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1175 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1176 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1178 :param list of string prop_names: list of name of property fields for those we want the default value
1179 :return: map of property field names to their default value
1182 prop = obj.pool.get('ir.property')
1184 for prop_name in prop_names:
1185 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1188 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1189 prop = obj.pool.get('ir.property')
1190 vids = [obj._name + ',' + str(oid) for oid in ids]
1192 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1193 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1195 domain = [('res_id', 'in', vids)] + domain
1196 return prop.search(cr, uid, domain, context=context)
1198 # TODO: to rewrite more clean
1199 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1203 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1205 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1207 default_val = self._get_default(obj, cr, uid, prop_name, context)
1209 if id_val is not default_val:
1210 def_id = self._field_get(cr, uid, obj._name, prop_name)
1211 company = obj.pool.get('res.company')
1212 cid = company._company_default_get(cr, uid, obj._name, def_id,
1214 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1216 prop = obj.pool.get('ir.property')
1217 return prop.create(cr, uid, {
1218 'name': propdef.name,
1220 'res_id': obj._name+','+str(id),
1222 'fields_id': def_id,
1227 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1228 prop = obj.pool.get('ir.property')
1229 # get the default values (for res_id = False) for the property fields
1230 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1232 # build the dictionary that will be returned
1235 res[id] = default_val.copy()
1237 for prop_name in prop_names:
1238 property_field = obj._all_columns.get(prop_name).column
1239 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1240 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1241 # in order to make a name_get in batch for all the ids needed.
1244 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1245 obj_reference = obj._name + ',' + str(id)
1246 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1248 res[id][prop_name] = value
1249 # Check existence as root (as seeing the name of a related
1250 # object depends on access right of source document,
1251 # not target, so user may not have access) in order to avoid
1252 # pointing on an unexisting record.
1253 if property_destination_obj:
1254 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1255 name_get_ids[id] = res[id][prop_name].id
1257 res[id][prop_name] = False
1258 if property_destination_obj:
1259 # name_get as root (as seeing the name of a related
1260 # object depends on access right of source document,
1261 # not target, so user may not have access.)
1262 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1263 # the property field is a m2o, we need to return a tuple with (id, name)
1264 for k, v in name_get_ids.iteritems():
1265 if res[k][prop_name]:
1266 res[k][prop_name] = (v , name_get_values.get(v))
1269 def _field_get(self, cr, uid, model_name, prop):
1270 if not self.field_id.get(cr.dbname):
1271 cr.execute('SELECT id \
1272 FROM ir_model_fields \
1273 WHERE name=%s AND model=%s', (prop, model_name))
1275 self.field_id[cr.dbname] = res and res[0]
1276 return self.field_id[cr.dbname]
1278 def __init__(self, obj_prop, **args):
1279 # TODO remove obj_prop parameter (use many2one type)
1281 function.__init__(self, self._fnct_read, False, self._fnct_write,
1282 obj_prop, multi='properties', **args)
1288 def field_to_dict(self, cr, user, context, field):
1289 """ Return a dictionary representation of a field.
1291 The string, help, and selection attributes (if any) are untranslated. This
1292 representation is the one returned by fields_get() (fields_get() will do
1297 res = {'type': field._type}
1298 # This additional attributes for M2M and function field is added
1299 # because we need to display tooltip with this additional information
1300 # when client is started in debug mode.
1301 if isinstance(field, function):
1302 res['function'] = field._fnct and field._fnct.func_name or False
1303 res['store'] = field.store
1304 if isinstance(field.store, dict):
1305 res['store'] = str(field.store)
1306 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1307 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1308 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1309 res['func_obj'] = field._obj or False
1310 if isinstance(field, many2many):
1311 res['related_columns'] = list((field._id1, field._id2))
1312 res['third_table'] = field._rel
1313 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1314 'change_default', 'translate', 'help', 'select', 'selectable'):
1315 if getattr(field, arg):
1316 res[arg] = getattr(field, arg)
1317 for arg in ('digits', 'invisible', 'filters'):
1318 if getattr(field, arg, None):
1319 res[arg] = getattr(field, arg)
1322 res['string'] = field.string
1324 res['help'] = field.help
1326 if hasattr(field, 'selection'):
1327 if isinstance(field.selection, (tuple, list)):
1328 res['selection'] = field.selection
1330 # call the 'dynamic selection' function
1331 res['selection'] = field.selection(self, cr, user, context)
1332 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1333 res['relation'] = field._obj
1334 res['domain'] = field._domain
1335 res['context'] = field._context
1340 class column_info(object):
1341 """Struct containing details about an osv column, either one local to
1342 its model, or one inherited via _inherits.
1344 :attr name: name of the column
1345 :attr column: column instance, subclass of osv.fields._column
1346 :attr parent_model: if the column is inherited, name of the model
1347 that contains it, None for local columns.
1348 :attr parent_column: the name of the column containing the m2o
1349 relationship to the parent model that contains
1350 this column, None for local columns.
1351 :attr original_parent: if the column is inherited, name of the original
1352 parent model that contains it i.e in case of multilevel
1353 inheritence, None for local columns.
1355 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1357 self.column = column
1358 self.parent_model = parent_model
1359 self.parent_column = parent_column
1360 self.original_parent = original_parent
1362 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: