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="set null", 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
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 set_memory(self, cr, obj, id, name, value, user=None, context=None):
116 raise Exception(_('Not implemented set_memory method !'))
118 def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
119 raise Exception(_('Not implemented get_memory method !'))
121 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
122 raise Exception(_('undefined get method !'))
124 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
125 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
126 res = obj.read(cr, uid, ids, [name], context=context)
127 return [x[name] for x in res]
129 def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
130 raise Exception(_('Not implemented search_memory method !'))
133 # ---------------------------------------------------------
135 # ---------------------------------------------------------
136 class boolean(_column):
139 _symbol_f = lambda x: x and 'True' or 'False'
140 _symbol_set = (_symbol_c, _symbol_f)
142 class integer(_column):
145 _symbol_f = lambda x: int(x or 0)
146 _symbol_set = (_symbol_c, _symbol_f)
147 _symbol_get = lambda self,x: x or 0
149 class integer_big(_column):
150 """Experimental 64 bit integer column type, currently unused.
152 TODO: this field should work fine for values up
153 to 32 bits, but greater values will not fit
154 in the XML-RPC int type, so a specific
155 get() method is needed to pass them as floats,
156 like what we do for integer functional fields.
158 _type = 'integer_big'
159 # do not reference the _symbol_* of integer class, as that would possibly
160 # unbind the lambda functions
162 _symbol_f = lambda x: int(x or 0)
163 _symbol_set = (_symbol_c, _symbol_f)
164 _symbol_get = lambda self,x: x or 0
166 class reference(_column):
168 def __init__(self, string, selection, size, **args):
169 _column.__init__(self, string=string, size=size, selection=selection, **args)
175 def __init__(self, string, size, **args):
176 _column.__init__(self, string=string, size=size, **args)
177 self._symbol_set = (self._symbol_c, self._symbol_set_char)
179 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
180 def _symbol_set_char(self, symb):
182 # * we need to remove the "symb==False" from the next line BUT
183 # for now too many things rely on this broken behavior
184 # * the symb==None test should be common to all data types
185 if symb == None or symb == False:
188 # we need to convert the string to a unicode object to be able
189 # to evaluate its length (and possibly truncate it) reliably
190 u_symb = tools.ustr(symb)
192 return u_symb[:self.size].encode('utf8')
200 class float(_column):
203 _symbol_f = lambda x: __builtin__.float(x or 0.0)
204 _symbol_set = (_symbol_c, _symbol_f)
205 _symbol_get = lambda self,x: x or 0.0
207 def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
208 _column.__init__(self, string=string, **args)
210 self.digits_compute = digits_compute
213 def digits_change(self, cr):
214 if self.digits_compute:
215 t = self.digits_compute(cr)
216 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
223 """ Returns the current date in a format fit for being a
224 default value to a ``date`` field.
226 This method should be provided as is to the _defaults dict, it
227 should not be called.
229 return DT.date.today().strftime(
230 tools.DEFAULT_SERVER_DATE_FORMAT)
232 class datetime(_column):
236 """ Returns the current datetime in a format fit for being a
237 default value to a ``datetime`` field.
239 This method should be provided as is to the _defaults dict, it
240 should not be called.
242 return DT.datetime.now().strftime(
243 tools.DEFAULT_SERVER_DATETIME_FORMAT)
249 """ Returns the current time in a format fit for being a
250 default value to a ``time`` field.
252 This method should be proivided as is to the _defaults dict,
253 it should not be called.
255 return DT.datetime.now().strftime(
256 tools.DEFAULT_SERVER_TIME_FORMAT)
258 class binary(_column):
261 _symbol_f = lambda symb: symb and Binary(symb) or None
262 _symbol_set = (_symbol_c, _symbol_f)
263 _symbol_get = lambda self, x: x and str(x)
265 _classic_read = False
268 def __init__(self, string='unknown', filters=None, **args):
269 _column.__init__(self, string=string, **args)
270 self.filters = filters
272 def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
285 # If client is requesting only the size of the field, we return it instead
286 # of the content. Presumably a separate request will be done to read the actual
287 # content if it's needed at some point.
288 # TODO: after 6.0 we should consider returning a dict with size and content instead of
289 # having an implicit convention for the value
290 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
291 res[i] = tools.human_size(long(val))
299 class selection(_column):
302 def __init__(self, selection, string='unknown', **args):
303 _column.__init__(self, string=string, **args)
304 self.selection = selection
306 # ---------------------------------------------------------
308 # ---------------------------------------------------------
311 # Values: (0, 0, { fields }) create
312 # (1, ID, { fields }) update
313 # (2, ID) remove (delete)
314 # (3, ID) unlink one (target id or target of relation)
316 # (5) unlink all (only valid for one2many)
318 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
319 class one2one(_column):
320 _classic_read = False
321 _classic_write = True
324 def __init__(self, obj, string='unknown', **args):
325 warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
326 _column.__init__(self, string=string, **args)
329 def set(self, cr, obj_src, id, field, act, user=None, context=None):
332 obj = obj_src.pool.get(self._obj)
333 self._table = obj_src.pool.get(self._obj)._table
335 id_new = obj.create(cr, user, act[1])
336 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
338 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
339 id = cr.fetchone()[0]
340 obj.write(cr, user, [id], act[1], context=context)
342 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
343 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
346 class many2one(_column):
347 _classic_read = False
348 _classic_write = True
351 _symbol_f = lambda x: x or None
352 _symbol_set = (_symbol_c, _symbol_f)
354 def __init__(self, obj, string='unknown', **args):
355 _column.__init__(self, string=string, **args)
358 def set_memory(self, cr, obj, id, field, values, user=None, context=None):
359 obj.datas.setdefault(id, {})
360 obj.datas[id][field] = values
362 def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
365 result[id] = obj.datas[id].get(name, False)
367 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
368 # we use uid=1 because the visibility of a many2one field value (just id and name)
369 # must be the access right of the parent form and not the linked object itself.
370 obj = obj.pool.get(self._obj)
371 records = dict(obj.name_get(cr, 1,
372 list(set([x for x in result.values() if x and isinstance(x, (int,long))])),
375 if result[id] in records:
376 result[id] = (result[id], records[result[id]])
382 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
390 res[r['id']] = r[name]
392 res.setdefault(id, '')
393 obj = obj.pool.get(self._obj)
395 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
396 # we use uid=1 because the visibility of a many2one field value (just id and name)
397 # must be the access right of the parent form and not the linked object itself.
398 records = dict(obj.name_get(cr, 1,
399 list(set([x for x in res.values() if isinstance(x, (int,long))])),
402 if res[id] in records:
403 res[id] = (res[id], records[res[id]])
408 def set(self, cr, obj_src, id, field, values, user=None, context=None):
411 obj = obj_src.pool.get(self._obj)
412 self._table = obj_src.pool.get(self._obj)._table
413 if type(values) == type([]):
416 id_new = obj.create(cr, act[2])
417 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
419 obj.write(cr, [act[1]], act[2], context=context)
421 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
422 elif act[0] == 3 or act[0] == 5:
423 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
425 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
428 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
430 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
432 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
433 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
436 class one2many(_column):
437 _classic_read = False
438 _classic_write = False
442 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
443 _column.__init__(self, string=string, **args)
445 self._fields_id = fields_id
447 #one2many can't be used as condition for defaults
448 assert(self.change_default != True)
450 def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
454 context = context.copy()
455 context.update(self._context)
461 ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
462 for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
463 if r[self._fields_id] in res:
464 res[r[self._fields_id]].append(r['id'])
467 def set_memory(self, cr, obj, id, field, values, user=None, context=None):
471 context = context.copy()
472 context.update(self._context)
475 obj = obj.pool.get(self._obj)
478 act[2][self._fields_id] = id
479 obj.create(cr, user, act[2], context=context)
481 obj.write(cr, user, [act[1]], act[2], context=context)
483 obj.unlink(cr, user, [act[1]], context=context)
485 obj.datas[act[1]][self._fields_id] = False
487 obj.datas[act[1]][self._fields_id] = id
489 for o in obj.datas.values():
490 if o[self._fields_id] == id:
491 o[self._fields_id] = False
493 for id2 in (act[2] or []):
494 obj.datas[id2][self._fields_id] = id
496 def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
497 raise _('Not Implemented')
499 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
503 context = context.copy()
504 context.update(self._context)
512 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
513 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
514 if r[self._fields_id] in res:
515 res[r[self._fields_id]].append(r['id'])
518 def set(self, cr, obj, id, field, values, user=None, context=None):
523 context = context.copy()
524 context.update(self._context)
525 context['no_store_function'] = True
528 _table = obj.pool.get(self._obj)._table
529 obj = obj.pool.get(self._obj)
532 act[2][self._fields_id] = id
533 id_new = obj.create(cr, user, act[2], context=context)
534 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
536 obj.write(cr, user, [act[1]], act[2], context=context)
538 obj.unlink(cr, user, [act[1]], context=context)
540 reverse_rel = obj._all_columns.get(self._fields_id)
541 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
542 # if the model has on delete cascade, just delete the row
543 if reverse_rel.column.ondelete == "cascade":
544 obj.unlink(cr, user, [act[1]], context=context)
546 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
548 # Must use write() to recompute parent_store structure if needed
549 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
551 reverse_rel = obj._all_columns.get(self._fields_id)
552 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
553 # if the model has on delete cascade, just delete the rows
554 if reverse_rel.column.ondelete == "cascade":
555 obj.unlink(cr, user, obj.search(cr, user, [(self._fields_id,'=',id)], context=context), context=context)
557 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
559 # Must use write() to recompute parent_store structure if needed
560 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
562 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
563 ids3 = map(lambda x:x[0], cr.fetchall())
564 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
567 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
568 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
572 # Values: (0, 0, { fields }) create
573 # (1, ID, { fields }) update (write fields to ID)
574 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
575 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
576 # (4, ID) link (add a relationship)
578 # (6, ?, ids) set a list of links
580 class many2many(_column):
581 _classic_read = False
582 _classic_write = False
585 def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args):
586 _column.__init__(self, string=string, **args)
589 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
590 'You used %s, which is not a valid SQL table name.')% (string,rel))
596 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
607 warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
608 DeprecationWarning, stacklevel=2)
609 obj = obj.pool.get(self._obj)
611 # static domains are lists, and are evaluated both here and on client-side, while string
612 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
613 # FIXME: make this distinction explicit in API!
614 domain = isinstance(self._domain, list) and self._domain or []
616 wquery = obj._where_calc(cr, user, domain, context=context)
617 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
618 from_c, where_c, where_params = wquery.get_sql()
620 where_c = ' AND ' + where_c
622 if offset or self._limit:
623 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
628 if self._limit is not None:
629 limit_str = ' LIMIT %d' % self._limit
631 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
632 FROM %(rel)s, %(from_c)s \
633 WHERE %(rel)s.%(id1)s IN %%s \
634 AND %(rel)s.%(id2)s = %(tbl)s.id \
646 'order_by': order_by,
649 cr.execute(query, [tuple(ids),] + where_params)
650 for r in cr.fetchall():
651 res[r[1]].append(r[0])
654 def set(self, cr, obj, id, name, values, user=None, context=None):
659 obj = obj.pool.get(self._obj)
661 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
664 idnew = obj.create(cr, user, act[2], context=context)
665 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, idnew))
667 obj.write(cr, user, [act[1]], act[2], context=context)
669 obj.unlink(cr, user, [act[1]], context=context)
671 cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%s and '+ self._id2 + '=%s', (id, act[1]))
673 # following queries are in the same transaction - so should be relatively safe
674 cr.execute('SELECT 1 FROM '+self._rel+' WHERE '+self._id1+' = %s and '+self._id2+' = %s', (id, act[1]))
675 if not cr.fetchone():
676 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, act[1]))
678 cr.execute('delete from '+self._rel+' where ' + self._id1 + ' = %s', (id,))
681 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
683 d1 = ' and ' + ' and '.join(d1)
686 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)
688 for act_nbr in act[2]:
689 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s, %s)', (id, act_nbr))
692 # TODO: use a name_search
694 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
695 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
697 def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
700 result[id] = obj.datas[id].get(name, [])
703 def set_memory(self, cr, obj, id, name, values, user=None, context=None):
707 # TODO: use constants instead of these magic numbers
709 raise _('Not Implemented')
711 raise _('Not Implemented')
713 raise _('Not Implemented')
715 raise _('Not Implemented')
717 raise _('Not Implemented')
719 raise _('Not Implemented')
721 obj.datas[id][name] = act[2]
724 def get_nice_size(value):
726 if isinstance(value, (int,long)):
728 elif value: # this is supposed to be a string
730 return tools.human_size(size)
732 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
733 # and http://bugs.python.org/issue10066
734 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
736 def sanitize_binary_value(value):
737 # binary fields should be 7-bit ASCII base64-encoded data,
738 # but we do additional sanity checks to make sure the values
739 # are not something else that won't pass via XML-RPC
740 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
741 # these builtin types are meant to pass untouched
744 # Handle invalid bytes values that will cause problems
745 # for XML-RPC. See for more info:
746 # - http://bugs.python.org/issue10066
747 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
749 # Coercing to unicode would normally allow it to properly pass via
750 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
751 # (this works for _any_ byte values, thanks to the fallback
752 # to latin-1 passthrough encoding when decoding to unicode)
753 value = tools.ustr(value)
755 # Due to Python bug #10066 this could still yield invalid XML
756 # bytes, specifically in the low byte range, that will crash
757 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
758 # So check for low bytes values, and if any, perform
759 # base64 encoding - not very smart or useful, but this is
760 # our last resort to avoid crashing the request.
761 if invalid_xml_low_bytes.search(value):
762 # b64-encode after restoring the pure bytes with latin-1
763 # passthrough encoding
764 value = base64.b64encode(value.encode('latin-1'))
769 # ---------------------------------------------------------
771 # ---------------------------------------------------------
772 class function(_column):
774 A field whose value is computed by a function (rather
775 than being read from the database).
777 :param fnct: the callable that will compute the field value.
778 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
779 :param fnct_inv: the callable that will allow writing values in that field
780 (if not provided, the field is read-only).
781 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
783 :param str type: type of the field simulated by the function field
784 :param fnct_search: the callable that allows searching on the field
785 (if not provided, search will not return any result).
786 :param store: store computed value in database
787 (see :ref:`The *store* parameter <field-function-store>`).
788 :type store: True or dict specifying triggers for field computation
789 :param multi: name of batch for batch computation of function fields.
790 All fields with the same batch name will be computed by
791 a single function call. This changes the signature of the
794 .. _field-function-fnct: The ``fnct`` parameter
796 .. rubric:: The ``fnct`` parameter
798 The callable implementing the function field must have the following signature:
800 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
802 Implements the function field.
804 :param orm_template model: model to which the field belongs (should be ``self`` for
806 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
807 list of field names to compute.
808 :type field_name(s): str | [str]
809 :param arg: arbitrary value passed when declaring the function field
811 :return: mapping of ``ids`` to computed values, or if multi is provided,
812 to a map of field_names to computed values
814 The values in the returned dictionary must be of the type specified by the type
815 argument in the field declaration.
817 Here is an example with a simple function ``char`` function field::
820 def compute(self, cr, uid, ids, field_name, arg, context):
824 _columns['my_char'] = fields.function(compute, type='char', size=50)
826 # when called with ``ids=[1,2,3]``, ``compute`` could return:
830 3: False # null values should be returned explicitly too
833 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
834 of the field names that should be computed. Each value in the returned
835 dictionary must then be a dictionary mapping field names to values.
837 Here is an example where two function fields (``name`` and ``age``)
838 are both computed by a single function field::
841 def compute(self, cr, uid, ids, field_names, arg, context):
845 _columns['name'] = fields.function(compute_person_data, type='char',\
846 size=50, multi='person_data')
847 _columns[''age'] = fields.function(compute_person_data, type='integer',\
850 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
852 1: {'name': 'Bob', 'age': 23},
853 2: {'name': 'Sally', 'age': 19},
854 3: {'name': 'unknown', 'age': False}
857 .. _field-function-fnct-inv:
859 .. rubric:: The ``fnct_inv`` parameter
861 This callable implements the write operation for the function field
862 and must have the following signature:
864 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
866 Callable that implements the ``write`` operation for the function field.
868 :param orm_template model: model to which the field belongs (should be ``self`` for
870 :param int id: the identifier of the object to write on
871 :param str field_name: name of the field to set
872 :param fnct_inv_arg: arbitrary value passed when declaring the function field
875 When writing values for a function field, the ``multi`` parameter is ignored.
877 .. _field-function-fnct-search:
879 .. rubric:: The ``fnct_search`` parameter
881 This callable implements the search operation for the function field
882 and must have the following signature:
884 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
886 Callable that implements the ``search`` operation for the function field by expanding
887 a search criterion based on the function field into a new domain based only on
888 columns that are stored in the database.
890 :param orm_template model: model to which the field belongs (should be ``self`` for
892 :param orm_template model_again: same value as ``model`` (seriously! this is for backwards
894 :param str field_name: name of the field to search on
895 :param list criterion: domain component specifying the search criterion on the field.
897 :return: domain to use instead of ``criterion`` when performing the search.
898 This new domain must be based only on columns stored in the database, as it
899 will be used directly without any translation.
901 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
902 The most generic way to implement ``fnct_search`` is to directly search for the records that
903 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
904 ``[('id','in',[1,3,5])]``.
906 .. _field-function-store:
908 .. rubric:: The ``store`` parameter
910 The ``store`` parameter allows caching the result of the field computation in the
911 database, and defining the triggers that will invalidate that cache and force a
912 recomputation of the function field.
913 When not provided, the field is computed every time its value is read.
914 The value of ``store`` may be either ``True`` (to recompute the field value whenever
915 any field in the same record is modified), or a dictionary specifying a more
916 flexible set of recomputation triggers.
918 A trigger specification is a dictionary that maps the names of the models that
919 will trigger the computation, to a tuple describing the trigger rule, in the
923 'trigger_model': (mapping_function,
924 ['trigger_field1', 'trigger_field2'],
928 A trigger rule is defined by a 3-item tuple where:
930 * The ``mapping_function`` is defined as follows:
932 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
934 Callable that maps record ids of a trigger model to ids of the
935 corresponding records in the source model (whose field values
936 need to be recomputed).
938 :param orm_template model: trigger_model
939 :param list trigger_ids: ids of the records of trigger_model that were
942 :return: list of ids of the source model whose function field values
943 need to be recomputed
945 * The second item is a list of the fields who should act as triggers for
946 the computation. If an empty list is given, all fields will act as triggers.
947 * The last item is the priority, used to order the triggers when processing them
948 after any write operation on a model that has function field triggers. The
949 default priority is 10.
951 In fact, setting store = True is the same as using the following trigger dict::
954 'model_itself': (lambda self, cr, uid, ids, context: ids,
960 _classic_read = False
961 _classic_write = False
967 # multi: compute several fields in one call
969 def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, store=False, multi=False, **args):
970 _column.__init__(self, **args)
973 self._fnct_inv = fnct_inv
976 if 'relation' in args:
977 self._obj = args['relation']
979 self.digits = args.get('digits', (16,2))
980 self.digits_compute = args.get('digits_compute', None)
982 self._fnct_inv_arg = fnct_inv_arg
986 self._fnct_search = fnct_search
989 if not fnct_search and not store:
990 self.selectable = False
993 if self._type != 'many2one':
994 # m2o fields need to return tuples with name_get, not just foreign keys
995 self._classic_read = True
996 self._classic_write = True
998 self._symbol_get=lambda x:x and str(x)
1001 self._symbol_c = float._symbol_c
1002 self._symbol_f = float._symbol_f
1003 self._symbol_set = float._symbol_set
1005 if type == 'boolean':
1006 self._symbol_c = boolean._symbol_c
1007 self._symbol_f = boolean._symbol_f
1008 self._symbol_set = boolean._symbol_set
1010 if type in ['integer','integer_big']:
1011 self._symbol_c = integer._symbol_c
1012 self._symbol_f = integer._symbol_f
1013 self._symbol_set = integer._symbol_set
1015 def digits_change(self, cr):
1016 if self.digits_compute:
1017 t = self.digits_compute(cr)
1018 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
1022 def search(self, cr, uid, obj, name, args, context=None):
1023 if not self._fnct_search:
1024 #CHECKME: should raise an exception
1026 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1028 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1032 field_type = obj._columns[field]._type
1033 if field_type == "many2one":
1034 # make the result a tuple if it is not already one
1035 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1036 obj_model = obj.pool.get(obj._columns[field].relation)
1037 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1038 result = (value, dict_names[value])
1040 if field_type == 'binary':
1041 if context.get('bin_size', False):
1042 # client requests only the size of binary fields
1043 result = get_nice_size(value)
1045 result = sanitize_binary_value(value)
1047 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1048 # integer/long values greater than 2^31-1 are not supported
1049 # in pure XMLRPC, so we have to pass them as floats :-(
1050 # This is not needed for stored fields and non-functional integer
1051 # fields, as their values are constrained by the database backend
1052 # to the same 32bits signed int limit.
1053 result = float(value)
1056 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1057 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1059 if self._multi and id in result:
1060 for field, value in result[id].iteritems():
1062 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1063 elif result.get(id):
1064 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1069 def set(self, cr, obj, id, name, value, user=None, context=None):
1073 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1076 # ---------------------------------------------------------
1078 # ---------------------------------------------------------
1080 class related(function):
1081 """Field that points to some data inside another field of the current record.
1086 'foo_id': fields.many2one('my.foo', 'Foo'),
1087 'bar': fields.related('frol', 'foo_id', type='char', string='Frol of Foo'),
1091 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1092 self._field_get2(cr, uid, obj, context)
1093 i = len(self._arg)-1
1096 if type(sarg) in [type([]), type( (1,) )]:
1097 where = [(self._arg[i], 'in', sarg)]
1099 where = [(self._arg[i], '=', sarg)]
1101 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1103 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1105 return [(self._arg[0], 'in', sarg)]
1107 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1108 self._field_get2(cr, uid, obj, context=context)
1109 if type(ids) != type([]):
1111 objlst = obj.browse(cr, uid, ids)
1115 for i in range(len(self.arg)):
1116 if not t_data: break
1117 field_detail = self._relations[i]
1118 if not t_data[self.arg[i]]:
1119 if self._type not in ('one2many', 'many2many'):
1122 elif field_detail['type'] in ('one2many', 'many2many'):
1123 if self._type != "many2one":
1125 t_data = t_data[self.arg[i]][0]
1130 t_data = t_data[self.arg[i]]
1132 model = obj.pool.get(self._relations[-1]['object'])
1133 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1135 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1136 self._field_get2(cr, uid, obj, context)
1137 if not ids: return {}
1138 relation = obj._name
1139 if self._type in ('one2many', 'many2many'):
1140 res = dict([(i, []) for i in ids])
1142 res = {}.fromkeys(ids, False)
1144 objlst = obj.browse(cr, 1, ids, context=context)
1149 relation = obj._name
1150 for i in range(len(self.arg)):
1151 field_detail = self._relations[i]
1152 relation = field_detail['object']
1154 if not t_data[self.arg[i]]:
1160 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1161 t_data = t_data[self.arg[i]][0]
1163 t_data = t_data[self.arg[i]]
1164 if type(t_data) == type(objlst[0]):
1165 res[data.id] = t_data.id
1167 res[data.id] = t_data
1168 if self._type=='many2one':
1169 ids = filter(None, res.values())
1171 # name_get as root, as seeing the name of a related
1172 # object depends on access right of source document,
1173 # not target, so user may not have access.
1174 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1177 res[r] = (res[r], ng[res[r]])
1178 elif self._type in ('one2many', 'many2many'):
1181 res[r] = [x.id for x in res[r]]
1184 def __init__(self, *arg, **args):
1186 self._relations = []
1187 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1188 if self.store is True:
1189 # TODO: improve here to change self.store = {...} according to related objects
1192 def _field_get2(self, cr, uid, obj, context=None):
1195 obj_name = obj._name
1196 for i in range(len(self._arg)):
1197 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1198 self._relations.append({
1203 if f.get('relation',False):
1204 obj_name = f['relation']
1205 self._relations[-1]['relation'] = f['relation']
1207 # ---------------------------------------------------------
1209 # ---------------------------------------------------------
1211 class dummy(function):
1212 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1215 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1218 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1221 def __init__(self, *arg, **args):
1223 self._relations = []
1224 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1226 # ---------------------------------------------------------
1228 # ---------------------------------------------------------
1229 class serialized(_column):
1230 def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
1231 self._serialize_func = serialize_func
1232 self._deserialize_func = deserialize_func
1234 self._symbol_set = (self._symbol_c, self._serialize_func)
1235 self._symbol_get = self._deserialize_func
1236 super(serialized, self).__init__(string=string, **args)
1238 # TODO: review completly this class for speed improvement
1239 class property(function):
1241 def _get_default(self, obj, cr, uid, prop_name, context=None):
1242 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1244 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1245 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1247 :param list of string prop_names: list of name of property fields for those we want the default value
1248 :return: map of property field names to their default value
1251 prop = obj.pool.get('ir.property')
1253 for prop_name in prop_names:
1254 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1257 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1258 prop = obj.pool.get('ir.property')
1259 vids = [obj._name + ',' + str(oid) for oid in ids]
1261 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1262 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1264 domain = [('res_id', 'in', vids)] + domain
1265 return prop.search(cr, uid, domain, context=context)
1267 # TODO: to rewrite more clean
1268 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1272 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1274 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1276 default_val = self._get_default(obj, cr, uid, prop_name, context)
1278 if id_val is not default_val:
1279 def_id = self._field_get(cr, uid, obj._name, prop_name)
1280 company = obj.pool.get('res.company')
1281 cid = company._company_default_get(cr, uid, obj._name, def_id,
1283 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1285 prop = obj.pool.get('ir.property')
1286 return prop.create(cr, uid, {
1287 'name': propdef.name,
1289 'res_id': obj._name+','+str(id),
1291 'fields_id': def_id,
1296 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1297 prop = obj.pool.get('ir.property')
1298 # get the default values (for res_id = False) for the property fields
1299 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1301 # build the dictionary that will be returned
1304 res[id] = default_val.copy()
1306 for prop_name in prop_names:
1307 property_field = obj._all_columns.get(prop_name).column
1308 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1309 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1310 # in order to make a name_get in batch for all the ids needed.
1313 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1314 obj_reference = obj._name + ',' + str(id)
1315 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1317 res[id][prop_name] = value
1318 # Check existence as root (as seeing the name of a related
1319 # object depends on access right of source document,
1320 # not target, so user may not have access) in order to avoid
1321 # pointing on an unexisting record.
1322 if property_destination_obj:
1323 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1324 name_get_ids[id] = res[id][prop_name].id
1326 res[id][prop_name] = False
1327 if property_destination_obj:
1328 # name_get as root (as seeing the name of a related
1329 # object depends on access right of source document,
1330 # not target, so user may not have access.)
1331 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1332 # the property field is a m2o, we need to return a tuple with (id, name)
1333 for k, v in name_get_ids.iteritems():
1334 if res[k][prop_name]:
1335 res[k][prop_name] = (v , name_get_values.get(v))
1338 def _field_get(self, cr, uid, model_name, prop):
1339 if not self.field_id.get(cr.dbname):
1340 cr.execute('SELECT id \
1341 FROM ir_model_fields \
1342 WHERE name=%s AND model=%s', (prop, model_name))
1344 self.field_id[cr.dbname] = res and res[0]
1345 return self.field_id[cr.dbname]
1347 def __init__(self, obj_prop, **args):
1348 # TODO remove obj_prop parameter (use many2one type)
1350 function.__init__(self, self._fnct_read, False, self._fnct_write,
1351 obj_prop, multi='properties', **args)
1357 def field_to_dict(self, cr, user, context, field):
1358 """ Return a dictionary representation of a field.
1360 The string, help, and selection attributes (if any) are untranslated. This
1361 representation is the one returned by fields_get() (fields_get() will do
1366 res = {'type': field._type}
1367 # This additional attributes for M2M and function field is added
1368 # because we need to display tooltip with this additional information
1369 # when client is started in debug mode.
1370 if isinstance(field, function):
1371 res['function'] = field._fnct and field._fnct.func_name or False
1372 res['store'] = field.store
1373 if isinstance(field.store, dict):
1374 res['store'] = str(field.store)
1375 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1376 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1377 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1378 res['func_obj'] = field._obj or False
1379 if isinstance(field, many2many):
1380 res['related_columns'] = list((field._id1, field._id2))
1381 res['third_table'] = field._rel
1382 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1383 'change_default', 'translate', 'help', 'select', 'selectable'):
1384 if getattr(field, arg):
1385 res[arg] = getattr(field, arg)
1386 for arg in ('digits', 'invisible', 'filters'):
1387 if getattr(field, arg, None):
1388 res[arg] = getattr(field, arg)
1391 res['string'] = field.string
1393 res['help'] = field.help
1395 if hasattr(field, 'selection'):
1396 if isinstance(field.selection, (tuple, list)):
1397 res['selection'] = field.selection
1399 # call the 'dynamic selection' function
1400 res['selection'] = field.selection(self, cr, user, context)
1401 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1402 res['relation'] = field._obj
1403 res['domain'] = field._domain
1404 res['context'] = field._context
1409 class column_info(object):
1410 """Struct containing details about an osv column, either one local to
1411 its model, or one inherited via _inherits.
1413 :attr name: name of the column
1414 :attr column: column instance, subclass of osv.fields._column
1415 :attr parent_model: if the column is inherited, name of the model
1416 that contains it, None for local columns.
1417 :attr parent_column: the name of the column containing the m2o
1418 relationship to the parent model that contains
1419 this column, None for local columns.
1420 :attr original_parent: if the column is inherited, name of the original
1421 parent model that contains it i.e in case of multilevel
1422 inheritence, None for local columns.
1424 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1426 self.column = column
1427 self.parent_model = parent_model
1428 self.parent_column = parent_column
1429 self.original_parent = original_parent
1431 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: