1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 - relations (one2many, many2one, many2many)
28 * _classic_read: is a classic sql fields
40 from psycopg2 import Binary
42 import openerp.netsvc as netsvc
43 import openerp.tools as tools
44 from openerp.tools.translate import _
46 def _symbol_set(symb):
47 if symb == None or symb == False:
49 elif isinstance(symb, unicode):
50 return symb.encode('utf-8')
54 class _column(object):
55 """ Base of all fields, a database column
57 An instance of this object is a *description* of a database column. It will
58 not hold any data, but only provide the methods to manipulate data of an
59 ORM record or even prepare/update the database to hold such a field of data.
69 _symbol_f = _symbol_set
70 _symbol_set = (_symbol_c, _symbol_f)
73 def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete="set null", translate=False, select=False, manual=False, **args):
76 The 'manual' keyword argument specifies if the field is a custom one.
77 It corresponds to the 'state' column in ir_model_fields.
84 self.states = states or {}
86 self.readonly = readonly
87 self.required = required
89 self.help = args.get('help', '')
90 self.priority = priority
91 self.change_default = change_default
92 self.ondelete = ondelete
93 self.translate = translate
95 self._context = context
101 self.selectable = True
102 self.group_operator = args.get('group_operator', False)
105 setattr(self, a, args[a])
110 def set(self, cr, obj, id, name, value, user=None, context=None):
111 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
113 def set_memory(self, cr, obj, id, name, value, user=None, context=None):
114 raise Exception(_('Not implemented set_memory method !'))
116 def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
117 raise Exception(_('Not implemented get_memory method !'))
119 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
120 raise Exception(_('undefined get method !'))
122 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
123 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
124 res = obj.read(cr, uid, ids, [name], context=context)
125 return [x[name] for x in res]
127 def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
128 raise Exception(_('Not implemented search_memory method !'))
131 # ---------------------------------------------------------
133 # ---------------------------------------------------------
134 class boolean(_column):
137 _symbol_f = lambda x: x and 'True' or 'False'
138 _symbol_set = (_symbol_c, _symbol_f)
140 class integer(_column):
143 _symbol_f = lambda x: int(x or 0)
144 _symbol_set = (_symbol_c, _symbol_f)
145 _symbol_get = lambda self,x: x or 0
147 class integer_big(_column):
148 _type = 'integer_big'
149 # do not reference the _symbol_* of integer class, as that would possibly
150 # unbind the lambda functions
152 _symbol_f = lambda x: int(x or 0)
153 _symbol_set = (_symbol_c, _symbol_f)
154 _symbol_get = lambda self,x: x or 0
156 class reference(_column):
158 def __init__(self, string, selection, size, **args):
159 _column.__init__(self, string=string, size=size, selection=selection, **args)
165 def __init__(self, string, size, **args):
166 _column.__init__(self, string=string, size=size, **args)
167 self._symbol_set = (self._symbol_c, self._symbol_set_char)
169 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
170 def _symbol_set_char(self, symb):
172 # * we need to remove the "symb==False" from the next line BUT
173 # for now too many things rely on this broken behavior
174 # * the symb==None test should be common to all data types
175 if symb == None or symb == False:
178 # we need to convert the string to a unicode object to be able
179 # to evaluate its length (and possibly truncate it) reliably
180 u_symb = tools.ustr(symb)
182 return u_symb[:self.size].encode('utf8')
190 class float(_column):
193 _symbol_f = lambda x: __builtin__.float(x or 0.0)
194 _symbol_set = (_symbol_c, _symbol_f)
195 _symbol_get = lambda self,x: x or 0.0
197 def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
198 _column.__init__(self, string=string, **args)
200 self.digits_compute = digits_compute
203 def digits_change(self, cr):
204 if self.digits_compute:
205 t = self.digits_compute(cr)
206 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
213 """ Returns the current date in a format fit for being a
214 default value to a ``date`` field.
216 This method should be provided as is to the _defaults dict, it
217 should not be called.
219 return DT.date.today().strftime(
220 tools.DEFAULT_SERVER_DATE_FORMAT)
222 class datetime(_column):
226 """ Returns the current datetime in a format fit for being a
227 default value to a ``datetime`` field.
229 This method should be provided as is to the _defaults dict, it
230 should not be called.
232 return DT.datetime.now().strftime(
233 tools.DEFAULT_SERVER_DATETIME_FORMAT)
239 """ Returns the current time in a format fit for being a
240 default value to a ``time`` field.
242 This method should be proivided as is to the _defaults dict,
243 it should not be called.
245 return DT.datetime.now().strftime(
246 tools.DEFAULT_SERVER_TIME_FORMAT)
248 class binary(_column):
251 _symbol_f = lambda symb: symb and Binary(symb) or None
252 _symbol_set = (_symbol_c, _symbol_f)
253 _symbol_get = lambda self, x: x and str(x)
255 _classic_read = False
258 def __init__(self, string='unknown', filters=None, **args):
259 _column.__init__(self, string=string, **args)
260 self.filters = filters
262 def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
275 # If client is requesting only the size of the field, we return it instead
276 # of the content. Presumably a separate request will be done to read the actual
277 # content if it's needed at some point.
278 # TODO: after 6.0 we should consider returning a dict with size and content instead of
279 # having an implicit convention for the value
280 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
281 res[i] = tools.human_size(long(val))
289 class selection(_column):
292 def __init__(self, selection, string='unknown', **args):
293 _column.__init__(self, string=string, **args)
294 self.selection = selection
296 # ---------------------------------------------------------
298 # ---------------------------------------------------------
301 # Values: (0, 0, { fields }) create
302 # (1, ID, { fields }) update
303 # (2, ID) remove (delete)
304 # (3, ID) unlink one (target id or target of relation)
306 # (5) unlink all (only valid for one2many)
308 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
309 class one2one(_column):
310 _classic_read = False
311 _classic_write = True
314 def __init__(self, obj, string='unknown', **args):
315 warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
316 _column.__init__(self, string=string, **args)
319 def set(self, cr, obj_src, id, field, act, user=None, context=None):
322 obj = obj_src.pool.get(self._obj)
323 self._table = obj_src.pool.get(self._obj)._table
325 id_new = obj.create(cr, user, act[1])
326 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
328 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
329 id = cr.fetchone()[0]
330 obj.write(cr, user, [id], act[1], context=context)
332 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
333 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
336 class many2one(_column):
337 _classic_read = False
338 _classic_write = True
341 _symbol_f = lambda x: x or None
342 _symbol_set = (_symbol_c, _symbol_f)
344 def __init__(self, obj, string='unknown', **args):
345 _column.__init__(self, string=string, **args)
348 def set_memory(self, cr, obj, id, field, values, user=None, context=None):
349 obj.datas.setdefault(id, {})
350 obj.datas[id][field] = values
352 def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
355 result[id] = obj.datas[id].get(name, False)
357 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
358 # we use uid=1 because the visibility of a many2one field value (just id and name)
359 # must be the access right of the parent form and not the linked object itself.
360 obj = obj.pool.get(self._obj)
361 records = dict(obj.name_get(cr, 1,
362 list(set([x for x in result.values() if x and isinstance(x, (int,long))])),
365 if result[id] in records:
366 result[id] = (result[id], records[result[id]])
372 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
380 res[r['id']] = r[name]
382 res.setdefault(id, '')
383 obj = obj.pool.get(self._obj)
385 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
386 # we use uid=1 because the visibility of a many2one field value (just id and name)
387 # must be the access right of the parent form and not the linked object itself.
388 records = dict(obj.name_get(cr, 1,
389 list(set([x for x in res.values() if isinstance(x, (int,long))])),
392 if res[id] in records:
393 res[id] = (res[id], records[res[id]])
398 def set(self, cr, obj_src, id, field, values, user=None, context=None):
401 obj = obj_src.pool.get(self._obj)
402 self._table = obj_src.pool.get(self._obj)._table
403 if type(values) == type([]):
406 id_new = obj.create(cr, act[2])
407 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
409 obj.write(cr, [act[1]], act[2], context=context)
411 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
412 elif act[0] == 3 or act[0] == 5:
413 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
415 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
418 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
420 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
422 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
423 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
426 class one2many(_column):
427 _classic_read = False
428 _classic_write = False
432 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
433 _column.__init__(self, string=string, **args)
435 self._fields_id = fields_id
437 #one2many can't be used as condition for defaults
438 assert(self.change_default != True)
440 def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
444 context = context.copy()
445 context.update(self._context)
451 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
452 for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
453 if r[self._fields_id] in res:
454 res[r[self._fields_id]].append(r['id'])
457 def set_memory(self, cr, obj, id, field, values, user=None, context=None):
461 context = context.copy()
462 context.update(self._context)
465 obj = obj.pool.get(self._obj)
468 act[2][self._fields_id] = id
469 obj.create(cr, user, act[2], context=context)
471 obj.write(cr, user, [act[1]], act[2], context=context)
473 obj.unlink(cr, user, [act[1]], context=context)
475 obj.datas[act[1]][self._fields_id] = False
477 obj.datas[act[1]][self._fields_id] = id
479 for o in obj.datas.values():
480 if o[self._fields_id] == id:
481 o[self._fields_id] = False
483 for id2 in (act[2] or []):
484 obj.datas[id2][self._fields_id] = id
486 def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
487 raise _('Not Implemented')
489 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
493 context = context.copy()
494 context.update(self._context)
502 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
503 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
504 if r[self._fields_id] in res:
505 res[r[self._fields_id]].append(r['id'])
508 def set(self, cr, obj, id, field, values, user=None, context=None):
513 context = context.copy()
514 context.update(self._context)
515 context['no_store_function'] = True
518 _table = obj.pool.get(self._obj)._table
519 obj = obj.pool.get(self._obj)
522 act[2][self._fields_id] = id
523 id_new = obj.create(cr, user, act[2], context=context)
524 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
526 obj.write(cr, user, [act[1]], act[2], context=context)
528 obj.unlink(cr, user, [act[1]], context=context)
530 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
532 # Must use write() to recompute parent_store structure if needed
533 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
535 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
537 # Must use write() to recompute parent_store structure if needed
538 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
540 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
541 ids3 = map(lambda x:x[0], cr.fetchall())
542 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
545 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
546 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
550 # Values: (0, 0, { fields }) create
551 # (1, ID, { fields }) update (write fields to ID)
552 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
553 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
554 # (4, ID) link (add a relationship)
556 # (6, ?, ids) set a list of links
558 class many2many(_column):
559 _classic_read = False
560 _classic_write = False
563 def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args):
564 _column.__init__(self, string=string, **args)
567 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
568 'You used %s, which is not a valid SQL table name.')% (string,rel))
574 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
585 warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
586 DeprecationWarning, stacklevel=2)
587 obj = obj.pool.get(self._obj)
589 # static domains are lists, and are evaluated both here and on client-side, while string
590 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
591 # FIXME: make this distinction explicit in API!
592 domain = isinstance(self._domain, list) and self._domain or []
594 wquery = obj._where_calc(cr, user, domain, context=context)
595 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
596 from_c, where_c, where_params = wquery.get_sql()
598 where_c = ' AND ' + where_c
600 if offset or self._limit:
601 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
606 if self._limit is not None:
607 limit_str = ' LIMIT %d' % self._limit
609 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
610 FROM %(rel)s, %(from_c)s \
611 WHERE %(rel)s.%(id1)s IN %%s \
612 AND %(rel)s.%(id2)s = %(tbl)s.id \
624 'order_by': order_by,
627 cr.execute(query, [tuple(ids),] + where_params)
628 for r in cr.fetchall():
629 res[r[1]].append(r[0])
632 def set(self, cr, obj, id, name, values, user=None, context=None):
637 obj = obj.pool.get(self._obj)
639 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
642 idnew = obj.create(cr, user, act[2], context=context)
643 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, idnew))
645 obj.write(cr, user, [act[1]], act[2], context=context)
647 obj.unlink(cr, user, [act[1]], context=context)
649 cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%s and '+ self._id2 + '=%s', (id, act[1]))
651 # following queries are in the same transaction - so should be relatively safe
652 cr.execute('SELECT 1 FROM '+self._rel+' WHERE '+self._id1+' = %s and '+self._id2+' = %s', (id, act[1]))
653 if not cr.fetchone():
654 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, act[1]))
656 cr.execute('update '+self._rel+' set '+self._id2+'=null where '+self._id2+'=%s', (id,))
659 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
661 d1 = ' and ' + ' and '.join(d1)
664 cr.execute('delete from '+self._rel+' where '+self._id1+'=%s AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+','.join(tables)+' WHERE '+self._rel+'.'+self._id1+'=%s AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2)
666 for act_nbr in act[2]:
667 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s, %s)', (id, act_nbr))
670 # TODO: use a name_search
672 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
673 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
675 def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
678 result[id] = obj.datas[id].get(name, [])
681 def set_memory(self, cr, obj, id, name, values, user=None, context=None):
685 # TODO: use constants instead of these magic numbers
687 raise _('Not Implemented')
689 raise _('Not Implemented')
691 raise _('Not Implemented')
693 raise _('Not Implemented')
695 raise _('Not Implemented')
697 raise _('Not Implemented')
699 obj.datas[id][name] = act[2]
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 def sanitize_binary_value(value):
711 # binary fields should be 7-bit ASCII base64-encoded data,
712 # but we do additional sanity checks to make sure the values
713 # are not something else that won't pass via xmlrpc
714 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
715 # these builtin types are meant to pass untouched
718 # For all other cases, handle the value as a binary string:
719 # it could be a 7-bit ASCII string (e.g base64 data), but also
720 # any 8-bit content from files, with byte values that cannot
721 # be passed inside XML!
723 # - http://bugs.python.org/issue10066
724 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
726 # One solution is to convert the byte-string to unicode,
727 # so it gets serialized as utf-8 encoded data (always valid XML)
728 # If invalid XML byte values were present, tools.ustr() uses
729 # the Latin-1 codec as fallback, which converts any 8-bit
730 # byte value, resulting in valid utf-8-encoded bytes
732 # >>> unicode('\xe1','latin1').encode('utf8') == '\xc3\xa1'
733 # Note: when this happens, decoding on the other endpoint
734 # is not likely to produce the expected output, but this is
735 # just a safety mechanism (in these cases base64 data or
736 # xmlrpc.Binary values should be used instead)
737 return tools.ustr(value)
740 # ---------------------------------------------------------
742 # ---------------------------------------------------------
743 class function(_column):
745 A field whose value is computed by a function (rather
746 than being read from the database).
748 :param fnct: the callable that will compute the field value.
749 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
750 :param fnct_inv: the callable that will allow writing values in that field
751 (if not provided, the field is read-only).
752 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
754 :param str type: type of the field simulated by the function field
755 :param fnct_search: the callable that allows searching on the field
756 (if not provided, search will not return any result).
757 :param store: store computed value in database
758 (see :ref:`The *store* parameter <field-function-store>`).
759 :type store: True or dict specifying triggers for field computation
760 :param multi: name of batch for batch computation of function fields.
761 All fields with the same batch name will be computed by
762 a single function call. This changes the signature of the
765 .. _field-function-fnct: The ``fnct`` parameter
767 .. rubric:: The ``fnct`` parameter
769 The callable implementing the function field must have the following signature:
771 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
773 Implements the function field.
775 :param orm_template model: model to which the field belongs (should be ``self`` for
777 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
778 list of field names to compute.
779 :type field_name(s): str | [str]
780 :param arg: arbitrary value passed when declaring the function field
782 :return: mapping of ``ids`` to computed values, or if multi is provided,
783 to a map of field_names to computed values
785 The values in the returned dictionary must be of the type specified by the type
786 argument in the field declaration.
788 Here is an example with a simple function ``char`` function field::
791 def compute(self, cr, uid, ids, field_name, arg, context):
795 _columns['my_char'] = fields.function(compute, type='char', size=50)
797 # when called with ``ids=[1,2,3]``, ``compute`` could return:
801 3: False # null values should be returned explicitly too
804 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
805 of the field names that should be computed. Each value in the returned
806 dictionary must then be a dictionary mapping field names to values.
808 Here is an example where two function fields (``name`` and ``age``)
809 are both computed by a single function field::
812 def compute(self, cr, uid, ids, field_names, arg, context):
816 _columns['name'] = fields.function(compute_person_data, type='char',\
817 size=50, multi='person_data')
818 _columns[''age'] = fields.function(compute_person_data, type='integer',\
821 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
823 1: {'name': 'Bob', 'age': 23},
824 2: {'name': 'Sally', 'age': 19},
825 3: {'name': 'unknown', 'age': False}
828 .. _field-function-fnct-inv:
830 .. rubric:: The ``fnct_inv`` parameter
832 This callable implements the write operation for the function field
833 and must have the following signature:
835 .. function:: fnct_inv(model, cr, uid, ids, field_name, field_value, fnct_inv_arg, context)
837 Callable that implements the ``write`` operation for the function field.
839 :param orm_template model: model to which the field belongs (should be ``self`` for
841 :param str field_name: name of the field to set
842 :param fnct_inv_arg: arbitrary value passed when declaring the function field
845 When writing values for a function field, the ``multi`` parameter is ignored.
847 .. _field-function-fnct-search:
849 .. rubric:: The ``fnct_search`` parameter
851 This callable implements the search operation for the function field
852 and must have the following signature:
854 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
856 Callable that implements the ``search`` operation for the function field by expanding
857 a search criterion based on the function field into a new domain based only on
858 columns that are stored in the database.
860 :param orm_template model: model to which the field belongs (should be ``self`` for
862 :param orm_template model_again: same value as ``model`` (seriously! this is for backwards
864 :param str field_name: name of the field to search on
865 :param list criterion: domain component specifying the search criterion on the field.
867 :return: domain to use instead of ``criterion`` when performing the search.
868 This new domain must be based only on columns stored in the database, as it
869 will be used directly without any translation.
871 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
872 The most generic way to implement ``fnct_search`` is to directly search for the records that
873 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
874 ``[('id','in',[1,3,5])]``.
876 .. _field-function-store:
878 .. rubric:: The ``store`` parameter
880 The ``store`` parameter allows caching the result of the field computation in the
881 database, and defining the triggers that will invalidate that cache and force a
882 recomputation of the function field.
883 When not provided, the field is computed every time its value is read.
884 The value of ``store`` may be either ``True`` (to recompute the field value whenever
885 any field in the same record is modified), or a dictionary specifying a more
886 flexible set of recomputation triggers.
888 A trigger specification is a dictionary that maps the names of the models that
889 will trigger the computation, to a tuple describing the trigger rule, in the
893 'trigger_model': (mapping_function,
894 ['trigger_field1', 'trigger_field2'],
898 A trigger rule is defined by a 3-item tuple where:
900 * The ``mapping_function`` is defined as follows:
902 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
904 Callable that maps record ids of a trigger model to ids of the
905 corresponding records in the source model (whose field values
906 need to be recomputed).
908 :param orm_template model: trigger_model
909 :param list trigger_ids: ids of the records of trigger_model that were
912 :return: list of ids of the source model whose function field values
913 need to be recomputed
915 * The second item is a list of the fields who should act as triggers for
916 the computation. If an empty list is given, all fields will act as triggers.
917 * The last item is the priority, used to order the triggers when processing them
918 after any write operation on a model that has function field triggers. The
919 default priority is 10.
921 In fact, setting store = True is the same as using the following trigger dict::
924 'model_itself': (lambda self, cr, uid, ids, context: ids,
930 _classic_read = False
931 _classic_write = False
937 # multi: compute several fields in one call
939 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):
940 _column.__init__(self, **args)
943 self._fnct_inv = fnct_inv
946 if 'relation' in args:
947 self._obj = args['relation']
949 self.digits = args.get('digits', (16,2))
950 self.digits_compute = args.get('digits_compute', None)
952 self._fnct_inv_arg = fnct_inv_arg
956 self._fnct_search = fnct_search
959 if not fnct_search and not store:
960 self.selectable = False
963 if self._type != 'many2one':
964 # m2o fields need to return tuples with name_get, not just foreign keys
965 self._classic_read = True
966 self._classic_write = True
968 self._symbol_get=lambda x:x and str(x)
971 self._symbol_c = float._symbol_c
972 self._symbol_f = float._symbol_f
973 self._symbol_set = float._symbol_set
975 if type == 'boolean':
976 self._symbol_c = boolean._symbol_c
977 self._symbol_f = boolean._symbol_f
978 self._symbol_set = boolean._symbol_set
980 if type in ['integer','integer_big']:
981 self._symbol_c = integer._symbol_c
982 self._symbol_f = integer._symbol_f
983 self._symbol_set = integer._symbol_set
985 def digits_change(self, cr):
986 if self.digits_compute:
987 t = self.digits_compute(cr)
988 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
992 def search(self, cr, uid, obj, name, args, context=None):
993 if not self._fnct_search:
994 #CHECKME: should raise an exception
996 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
998 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1002 field_type = obj._columns[field]._type
1003 if field_type == "many2one":
1004 # make the result a tuple if it is not already one
1005 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1006 obj_model = obj.pool.get(obj._columns[field].relation)
1007 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1008 result = (value, dict_names[value])
1010 if field_type == 'binary':
1011 if context.get('bin_size', False):
1012 # client requests only the size of binary fields
1013 result = get_nice_size(value)
1015 result = sanitize_binary_value(value)
1017 if field_type == "integer":
1018 result = tools.ustr(value)
1021 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1022 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1024 if self._multi and id in result:
1025 for field, value in result[id].iteritems():
1027 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1028 elif result.get(id):
1029 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1034 def set(self, cr, obj, id, name, value, user=None, context=None):
1038 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1041 # ---------------------------------------------------------
1043 # ---------------------------------------------------------
1045 class related(function):
1046 """Field that points to some data inside another field of the current record.
1051 'foo_id': fields.many2one('my.foo', 'Foo'),
1052 'bar': fields.related('frol', 'foo_id', type='char', string='Frol of Foo'),
1056 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1057 self._field_get2(cr, uid, obj, context)
1058 i = len(self._arg)-1
1061 if type(sarg) in [type([]), type( (1,) )]:
1062 where = [(self._arg[i], 'in', sarg)]
1064 where = [(self._arg[i], '=', sarg)]
1066 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1068 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1070 return [(self._arg[0], 'in', sarg)]
1072 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1073 self._field_get2(cr, uid, obj, context=context)
1074 if type(ids) != type([]):
1076 objlst = obj.browse(cr, uid, ids)
1080 for i in range(len(self.arg)):
1081 if not t_data: break
1082 field_detail = self._relations[i]
1083 if not t_data[self.arg[i]]:
1084 if self._type not in ('one2many', 'many2many'):
1087 elif field_detail['type'] in ('one2many', 'many2many'):
1088 if self._type != "many2one":
1090 t_data = t_data[self.arg[i]][0]
1095 t_data = t_data[self.arg[i]]
1097 model = obj.pool.get(self._relations[-1]['object'])
1098 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1100 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1101 self._field_get2(cr, uid, obj, context)
1102 if not ids: return {}
1103 relation = obj._name
1104 if self._type in ('one2many', 'many2many'):
1105 res = dict([(i, []) for i in ids])
1107 res = {}.fromkeys(ids, False)
1109 objlst = obj.browse(cr, 1, ids, context=context)
1114 relation = obj._name
1115 for i in range(len(self.arg)):
1116 field_detail = self._relations[i]
1117 relation = field_detail['object']
1119 if not t_data[self.arg[i]]:
1125 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1126 t_data = t_data[self.arg[i]][0]
1128 t_data = t_data[self.arg[i]]
1129 if type(t_data) == type(objlst[0]):
1130 res[data.id] = t_data.id
1132 res[data.id] = t_data
1133 if self._type=='many2one':
1134 ids = filter(None, res.values())
1136 # name_get as root, as seeing the name of a related
1137 # object depends on access right of source document,
1138 # not target, so user may not have access.
1139 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1142 res[r] = (res[r], ng[res[r]])
1143 elif self._type in ('one2many', 'many2many'):
1146 res[r] = [x.id for x in res[r]]
1149 def __init__(self, *arg, **args):
1151 self._relations = []
1152 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1153 if self.store is True:
1154 # TODO: improve here to change self.store = {...} according to related objects
1157 def _field_get2(self, cr, uid, obj, context=None):
1160 obj_name = obj._name
1161 for i in range(len(self._arg)):
1162 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1163 self._relations.append({
1168 if f.get('relation',False):
1169 obj_name = f['relation']
1170 self._relations[-1]['relation'] = f['relation']
1172 # ---------------------------------------------------------
1174 # ---------------------------------------------------------
1176 class dummy(function):
1177 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1180 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1183 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1186 def __init__(self, *arg, **args):
1188 self._relations = []
1189 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1191 # ---------------------------------------------------------
1193 # ---------------------------------------------------------
1194 class serialized(_column):
1195 def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
1196 self._serialize_func = serialize_func
1197 self._deserialize_func = deserialize_func
1199 self._symbol_set = (self._symbol_c, self._serialize_func)
1200 self._symbol_get = self._deserialize_func
1201 super(serialized, self).__init__(string=string, **args)
1204 # TODO: review completly this class for speed improvement
1205 class property(function):
1207 def _get_default(self, obj, cr, uid, prop_name, context=None):
1208 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[0][prop_name]
1210 def _get_defaults(self, obj, cr, uid, prop_name, context=None):
1211 prop = obj.pool.get('ir.property')
1212 domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)]
1213 ids = prop.search(cr, uid, domain, context=context)
1215 default_value = {}.fromkeys(prop_name, False)
1216 for prop_rec in prop.browse(cr, uid, ids, context=context):
1217 if default_value.get(prop_rec.fields_id.name, False):
1219 value = prop.get_by_record(cr, uid, prop_rec, context=context) or False
1220 default_value[prop_rec.fields_id.name] = value
1221 if value and (prop_rec.type == 'many2one'):
1222 replaces.setdefault(value._name, {})
1223 replaces[value._name][value.id] = True
1224 return default_value, replaces
1226 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1227 prop = obj.pool.get('ir.property')
1228 vids = [obj._name + ',' + str(oid) for oid in ids]
1230 domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
1231 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1233 domain = [('res_id', 'in', vids)] + domain
1234 return prop.search(cr, uid, domain, context=context)
1236 # TODO: to rewrite more clean
1237 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1241 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1243 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1245 default_val = self._get_default(obj, cr, uid, prop_name, context)
1247 if id_val is not default_val:
1248 def_id = self._field_get(cr, uid, obj._name, prop_name)
1249 company = obj.pool.get('res.company')
1250 cid = company._company_default_get(cr, uid, obj._name, def_id,
1252 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1254 prop = obj.pool.get('ir.property')
1255 return prop.create(cr, uid, {
1256 'name': propdef.name,
1258 'res_id': obj._name+','+str(id),
1260 'fields_id': def_id,
1266 def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
1267 properties = obj.pool.get('ir.property')
1268 domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
1269 domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in ids])]
1270 nids = properties.search(cr, uid, domain, context=context)
1271 default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context)
1275 res[id] = default_val.copy()
1277 brs = properties.browse(cr, uid, nids, context=context)
1279 value = properties.get_by_record(cr, uid, prop, context=context)
1280 res[prop.res_id.id][prop.fields_id.name] = value or False
1281 if value and (prop.type == 'many2one'):
1282 # check existence as root, as seeing the name of a related
1283 # object depends on access right of source document,
1284 # not target, so user may not have access.
1285 record_exists = obj.pool.get(value._name).exists(cr, 1, value.id)
1287 replaces.setdefault(value._name, {})
1288 replaces[value._name][value.id] = True
1290 res[prop.res_id.id][prop.fields_id.name] = False
1292 for rep in replaces:
1293 # search+name_get as root, as seeing the name of a related
1294 # object depends on access right of source document,
1295 # not target, so user may not have access.
1296 nids = obj.pool.get(rep).search(cr, 1, [('id','in',replaces[rep].keys())], context=context)
1297 replaces[rep] = dict(obj.pool.get(rep).name_get(cr, 1, nids, context=context))
1299 for prop in prop_name:
1301 if res[id][prop] and hasattr(res[id][prop], '_name'):
1302 res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False))
1307 def _field_get(self, cr, uid, model_name, prop):
1308 if not self.field_id.get(cr.dbname):
1309 cr.execute('SELECT id \
1310 FROM ir_model_fields \
1311 WHERE name=%s AND model=%s', (prop, model_name))
1313 self.field_id[cr.dbname] = res and res[0]
1314 return self.field_id[cr.dbname]
1316 def __init__(self, obj_prop, **args):
1317 # TODO remove obj_prop parameter (use many2one type)
1319 function.__init__(self, self._fnct_read, False, self._fnct_write,
1320 obj_prop, multi='properties', **args)
1326 def field_to_dict(self, cr, user, context, field):
1327 """ Return a dictionary representation of a field.
1329 The string, help, and selection attributes (if any) are untranslated. This
1330 representation is the one returned by fields_get() (fields_get() will do
1335 res = {'type': field._type}
1336 # This additional attributes for M2M and function field is added
1337 # because we need to display tooltip with this additional information
1338 # when client is started in debug mode.
1339 if isinstance(field, function):
1340 res['function'] = field._fnct and field._fnct.func_name or False
1341 res['store'] = field.store
1342 if isinstance(field.store, dict):
1343 res['store'] = str(field.store)
1344 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1345 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1346 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1347 res['func_obj'] = field._obj or False
1348 if isinstance(field, many2many):
1349 res['related_columns'] = list((field._id1, field._id2))
1350 res['third_table'] = field._rel
1351 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1352 'change_default', 'translate', 'help', 'select', 'selectable'):
1353 if getattr(field, arg):
1354 res[arg] = getattr(field, arg)
1355 for arg in ('digits', 'invisible', 'filters'):
1356 if getattr(field, arg, None):
1357 res[arg] = getattr(field, arg)
1360 res['string'] = field.string
1362 res['help'] = field.help
1364 if hasattr(field, 'selection'):
1365 if isinstance(field.selection, (tuple, list)):
1366 res['selection'] = field.selection
1368 # call the 'dynamic selection' function
1369 res['selection'] = field.selection(self, cr, user, context)
1370 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1371 res['relation'] = field._obj
1372 res['domain'] = field._domain
1373 res['context'] = field._context
1378 class column_info(object):
1379 """Struct containing details about an osv column, either one local to
1380 its model, or one inherited via _inherits.
1382 :attr name: name of the column
1383 :attr column: column instance, subclass of osv.fields._column
1384 :attr parent_model: if the column is inherited, name of the model
1385 that contains it, None for local columns.
1386 :attr parent_column: the name of the column containing the m2o
1387 relationship to the parent model that contains
1388 this column, None for local columns.
1390 def __init__(self, name, column, parent_model=None, parent_column=None):
1392 self.column = column
1393 self.parent_model = parent_model
1394 self.parent_column = parent_column
1396 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: