1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 - relations (one2many, many2one, many2many)
28 * _classic_read: is a classic sql fields
42 from psycopg2 import Binary
45 import openerp.netsvc as netsvc
46 import openerp.tools as tools
47 from openerp.tools.translate import _
49 def _symbol_set(symb):
50 if symb == None or symb == False:
52 elif isinstance(symb, unicode):
53 return symb.encode('utf-8')
57 class _column(object):
58 """ Base of all fields, a database column
60 An instance of this object is a *description* of a database column. It will
61 not hold any data, but only provide the methods to manipulate data of an
62 ORM record or even prepare/update the database to hold such a field of data.
72 _symbol_f = _symbol_set
73 _symbol_set = (_symbol_c, _symbol_f)
76 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):
79 The 'manual' keyword argument specifies if the field is a custom one.
80 It corresponds to the 'state' column in ir_model_fields.
87 self.states = states or {}
89 self.readonly = readonly
90 self.required = required
92 self.help = args.get('help', '')
93 self.priority = priority
94 self.change_default = change_default
95 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
96 self.translate = translate
98 self._context = context
104 self.selectable = True
105 self.group_operator = args.get('group_operator', False)
108 setattr(self, a, args[a])
113 def set(self, cr, obj, id, name, value, user=None, context=None):
114 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
116 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
117 raise Exception(_('undefined get method !'))
119 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
120 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
121 res = obj.read(cr, uid, ids, [name], context=context)
122 return [x[name] for x in res]
125 # ---------------------------------------------------------
127 # ---------------------------------------------------------
128 class boolean(_column):
131 _symbol_f = lambda x: x and 'True' or 'False'
132 _symbol_set = (_symbol_c, _symbol_f)
134 def __init__(self, string='unknown', required=False, **args):
135 super(boolean, self).__init__(string=string, required=required, **args)
137 warnings.warn("Making a boolean field `required` has no effect, as NULL values are "
138 "automatically turned into False", PendingDeprecationWarning, stacklevel=2)
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 def __init__(self, string='unknown', required=False, **args):
148 super(integer, self).__init__(string=string, required=required, **args)
150 warnings.warn("Making an integer field `required` has no effect, as NULL values are "
151 "automatically turned into 0", PendingDeprecationWarning, stacklevel=2)
153 class integer_big(_column):
154 """Experimental 64 bit integer column type, currently unused.
156 TODO: this field should work fine for values up
157 to 32 bits, but greater values will not fit
158 in the XML-RPC int type, so a specific
159 get() method is needed to pass them as floats,
160 like what we do for integer functional fields.
162 _type = 'integer_big'
163 # do not reference the _symbol_* of integer class, as that would possibly
164 # unbind the lambda functions
166 _symbol_f = lambda x: int(x or 0)
167 _symbol_set = (_symbol_c, _symbol_f)
168 _symbol_get = lambda self,x: x or 0
170 def __init__(self, string='unknown', required=False, **args):
171 super(integer_big, self).__init__(string=string, required=required, **args)
173 warnings.warn("Making an integer_big field `required` has no effect, as NULL values are "
174 "automatically turned into 0", PendingDeprecationWarning, stacklevel=2)
176 class reference(_column):
178 _classic_read = False # post-process to handle missing target
180 def __init__(self, string, selection, size, **args):
181 _column.__init__(self, string=string, size=size, selection=selection, **args)
183 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
185 # copy initial values fetched previously.
187 result[value['id']] = value[name]
189 model, res_id = value[name].split(',')
190 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
191 result[value['id']] = False
197 def __init__(self, string, size, **args):
198 _column.__init__(self, string=string, size=size, **args)
199 self._symbol_set = (self._symbol_c, self._symbol_set_char)
201 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
202 def _symbol_set_char(self, symb):
204 # * we need to remove the "symb==False" from the next line BUT
205 # for now too many things rely on this broken behavior
206 # * the symb==None test should be common to all data types
207 if symb == None or symb == False:
210 # we need to convert the string to a unicode object to be able
211 # to evaluate its length (and possibly truncate it) reliably
212 u_symb = tools.ustr(symb)
214 return u_symb[:self.size].encode('utf8')
222 class float(_column):
225 _symbol_f = lambda x: __builtin__.float(x or 0.0)
226 _symbol_set = (_symbol_c, _symbol_f)
227 _symbol_get = lambda self,x: x or 0.0
229 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
230 _column.__init__(self, string=string, required=required, **args)
232 self.digits_compute = digits_compute
234 warnings.warn("Making a float field `required` has no effect, as NULL values are "
235 "automatically turned into 0.0", PendingDeprecationWarning, stacklevel=2)
238 def digits_change(self, cr):
239 if self.digits_compute:
240 t = self.digits_compute(cr)
241 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
248 """ Returns the current date in a format fit for being a
249 default value to a ``date`` field.
251 This method should be provided as is to the _defaults dict, it
252 should not be called.
254 return DT.date.today().strftime(
255 tools.DEFAULT_SERVER_DATE_FORMAT)
257 class datetime(_column):
261 """ Returns the current datetime in a format fit for being a
262 default value to a ``datetime`` field.
264 This method should be provided as is to the _defaults dict, it
265 should not be called.
267 return DT.datetime.now().strftime(
268 tools.DEFAULT_SERVER_DATETIME_FORMAT)
274 """ Returns the current time in a format fit for being a
275 default value to a ``time`` field.
277 This method should be proivided as is to the _defaults dict,
278 it should not be called.
280 return DT.datetime.now().strftime(
281 tools.DEFAULT_SERVER_TIME_FORMAT)
283 class binary(_column):
286 _symbol_f = lambda symb: symb and Binary(symb) or None
287 _symbol_set = (_symbol_c, _symbol_f)
288 _symbol_get = lambda self, x: x and str(x)
290 _classic_read = False
293 def __init__(self, string='unknown', filters=None, **args):
294 _column.__init__(self, string=string, **args)
295 self.filters = filters
297 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
310 # If client is requesting only the size of the field, we return it instead
311 # of the content. Presumably a separate request will be done to read the actual
312 # content if it's needed at some point.
313 # TODO: after 6.0 we should consider returning a dict with size and content instead of
314 # having an implicit convention for the value
315 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
316 res[i] = tools.human_size(long(val))
321 class selection(_column):
324 def __init__(self, selection, string='unknown', **args):
325 _column.__init__(self, string=string, **args)
326 self.selection = selection
328 # ---------------------------------------------------------
330 # ---------------------------------------------------------
333 # Values: (0, 0, { fields }) create
334 # (1, ID, { fields }) update
335 # (2, ID) remove (delete)
336 # (3, ID) unlink one (target id or target of relation)
338 # (5) unlink all (only valid for one2many)
340 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
341 class one2one(_column):
342 _classic_read = False
343 _classic_write = True
346 def __init__(self, obj, string='unknown', **args):
347 warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
348 _column.__init__(self, string=string, **args)
351 def set(self, cr, obj_src, id, field, act, user=None, context=None):
354 obj = obj_src.pool.get(self._obj)
355 self._table = obj_src.pool.get(self._obj)._table
357 id_new = obj.create(cr, user, act[1])
358 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
360 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
361 id = cr.fetchone()[0]
362 obj.write(cr, user, [id], act[1], context=context)
364 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
365 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
368 class many2one(_column):
369 _classic_read = False
370 _classic_write = True
373 _symbol_f = lambda x: x or None
374 _symbol_set = (_symbol_c, _symbol_f)
376 def __init__(self, obj, string='unknown', **args):
377 _column.__init__(self, string=string, **args)
380 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
388 res[r['id']] = r[name]
390 res.setdefault(id, '')
391 obj = obj.pool.get(self._obj)
393 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
394 # we use uid=1 because the visibility of a many2one field value (just id and name)
395 # must be the access right of the parent form and not the linked object itself.
396 records = dict(obj.name_get(cr, 1,
397 list(set([x for x in res.values() if isinstance(x, (int,long))])),
400 if res[id] in records:
401 res[id] = (res[id], records[res[id]])
406 def set(self, cr, obj_src, id, field, values, user=None, context=None):
409 obj = obj_src.pool.get(self._obj)
410 self._table = obj_src.pool.get(self._obj)._table
411 if type(values) == type([]):
414 id_new = obj.create(cr, act[2])
415 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
417 obj.write(cr, [act[1]], act[2], context=context)
419 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
420 elif act[0] == 3 or act[0] == 5:
421 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
423 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
426 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
428 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
430 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
431 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
434 class one2many(_column):
435 _classic_read = False
436 _classic_write = False
440 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
441 _column.__init__(self, string=string, **args)
443 self._fields_id = fields_id
445 #one2many can't be used as condition for defaults
446 assert(self.change_default != True)
448 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
452 context = context.copy()
453 context.update(self._context)
461 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
462 for r in obj.pool.get(self._obj)._read_flat(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(self, cr, obj, id, field, values, user=None, context=None):
472 context = context.copy()
473 context.update(self._context)
474 context['no_store_function'] = True
477 _table = obj.pool.get(self._obj)._table
478 obj = obj.pool.get(self._obj)
481 act[2][self._fields_id] = id
482 id_new = obj.create(cr, user, act[2], context=context)
483 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
485 obj.write(cr, user, [act[1]], act[2], context=context)
487 obj.unlink(cr, user, [act[1]], context=context)
489 reverse_rel = obj._all_columns.get(self._fields_id)
490 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
491 # if the model has on delete cascade, just delete the row
492 if reverse_rel.column.ondelete == "cascade":
493 obj.unlink(cr, user, [act[1]], context=context)
495 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
497 # Must use write() to recompute parent_store structure if needed
498 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
500 reverse_rel = obj._all_columns.get(self._fields_id)
501 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
502 # if the model has on delete cascade, just delete the rows
503 if reverse_rel.column.ondelete == "cascade":
504 obj.unlink(cr, user, obj.search(cr, user, [(self._fields_id,'=',id)], context=context), context=context)
506 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
508 # Must use write() to recompute parent_store structure if needed
509 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
511 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
512 ids3 = map(lambda x:x[0], cr.fetchall())
513 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
516 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
517 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
521 # Values: (0, 0, { fields }) create
522 # (1, ID, { fields }) update (write fields to ID)
523 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
524 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
525 # (4, ID) link (add a relationship)
527 # (6, ?, ids) set a list of links
529 class many2many(_column):
530 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
531 low-level details of the intermediary relationship table transparently.
532 A many-to-many relationship is always symmetrical, and can be declared and accessed
533 from either endpoint model.
534 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
535 or id2 (destination foreign key column name) are not specified, the system will
536 provide default values. This will by default only allow one single symmetrical
537 many-to-many relationship between the source and destination model.
538 For multiple many-to-many relationship between the same models and for
539 relationships where source and destination models are the same, ``rel``, ``id1``
540 and ``id2`` should be specified explicitly.
542 :param str obj: destination model
543 :param str rel: optional name of the intermediary relationship table. If not specified,
544 a canonical name will be derived based on the alphabetically-ordered
545 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
546 Automatic naming is not possible when the source and destination are
547 the same, for obvious ambiguity reasons.
548 :param str id1: optional name for the column holding the foreign key to the current
549 model in the relationship table. If not specified, a canonical name
550 will be derived based on the model name (in the form: `src_model_id`).
551 :param str id2: optional name for the column holding the foreign key to the destination
552 model in the relationship table. If not specified, a canonical name
553 will be derived based on the model name (in the form: `dest_model_id`)
554 :param str string: field label
556 _classic_read = False
557 _classic_write = False
561 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
564 _column.__init__(self, string=string, **args)
566 if rel and '.' in rel:
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 _sql_names(self, source_model):
575 """Return the SQL names defining the structure of the m2m relationship table
577 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
578 local_col is the name of the column holding the current model's FK, and
579 dest_col is the name of the column holding the destination model's FK, and
581 tbl, col1, col2 = self._rel, self._id1, self._id2
582 if not all((tbl, col1, col2)):
583 # the default table name is based on the stable alphabetical order of tables
584 dest_model = source_model.pool.get(self._obj)
585 tables = tuple(sorted([source_model._table, dest_model._table]))
587 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
588 'is not possible when source and destination models are '\
590 tbl = '%s_%s_rel' % tables
592 col1 = '%s_id' % source_model._table
594 col2 = '%s_id' % dest_model._table
595 return (tbl, col1, col2)
597 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
608 warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
609 DeprecationWarning, stacklevel=2)
610 obj = model.pool.get(self._obj)
611 rel, id1, id2 = self._sql_names(model)
613 # static domains are lists, and are evaluated both here and on client-side, while string
614 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
615 # FIXME: make this distinction explicit in API!
616 domain = isinstance(self._domain, list) and self._domain or []
618 wquery = obj._where_calc(cr, user, domain, context=context)
619 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
620 from_c, where_c, where_params = wquery.get_sql()
622 where_c = ' AND ' + where_c
624 if offset or self._limit:
625 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
630 if self._limit is not None:
631 limit_str = ' LIMIT %d' % self._limit
633 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
634 FROM %(rel)s, %(from_c)s \
635 WHERE %(rel)s.%(id1)s IN %%s \
636 AND %(rel)s.%(id2)s = %(tbl)s.id \
648 'order_by': order_by,
651 cr.execute(query, [tuple(ids),] + where_params)
652 for r in cr.fetchall():
653 res[r[1]].append(r[0])
656 def set(self, cr, model, id, name, values, user=None, context=None):
661 rel, id1, id2 = self._sql_names(model)
662 obj = model.pool.get(self._obj)
664 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
667 idnew = obj.create(cr, user, act[2], context=context)
668 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
670 obj.write(cr, user, [act[1]], act[2], context=context)
672 obj.unlink(cr, user, [act[1]], context=context)
674 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
676 # following queries are in the same transaction - so should be relatively safe
677 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
678 if not cr.fetchone():
679 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
681 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
684 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
686 d1 = ' and ' + ' and '.join(d1)
689 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)
691 for act_nbr in act[2]:
692 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
695 # TODO: use a name_search
697 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
698 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
701 def get_nice_size(value):
703 if isinstance(value, (int,long)):
705 elif value: # this is supposed to be a string
707 return tools.human_size(size)
709 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
710 # and http://bugs.python.org/issue10066
711 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
713 def sanitize_binary_value(value):
714 # binary fields should be 7-bit ASCII base64-encoded data,
715 # but we do additional sanity checks to make sure the values
716 # are not something else that won't pass via XML-RPC
717 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
718 # these builtin types are meant to pass untouched
721 # Handle invalid bytes values that will cause problems
722 # for XML-RPC. See for more info:
723 # - http://bugs.python.org/issue10066
724 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
726 # Coercing to unicode would normally allow it to properly pass via
727 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
728 # (this works for _any_ byte values, thanks to the fallback
729 # to latin-1 passthrough encoding when decoding to unicode)
730 value = tools.ustr(value)
732 # Due to Python bug #10066 this could still yield invalid XML
733 # bytes, specifically in the low byte range, that will crash
734 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
735 # So check for low bytes values, and if any, perform
736 # base64 encoding - not very smart or useful, but this is
737 # our last resort to avoid crashing the request.
738 if invalid_xml_low_bytes.search(value):
739 # b64-encode after restoring the pure bytes with latin-1
740 # passthrough encoding
741 value = base64.b64encode(value.encode('latin-1'))
746 # ---------------------------------------------------------
748 # ---------------------------------------------------------
749 class function(_column):
751 A field whose value is computed by a function (rather
752 than being read from the database).
754 :param fnct: the callable that will compute the field value.
755 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
756 :param fnct_inv: the callable that will allow writing values in that field
757 (if not provided, the field is read-only).
758 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
760 :param str type: type of the field simulated by the function field
761 :param fnct_search: the callable that allows searching on the field
762 (if not provided, search will not return any result).
763 :param store: store computed value in database
764 (see :ref:`The *store* parameter <field-function-store>`).
765 :type store: True or dict specifying triggers for field computation
766 :param multi: name of batch for batch computation of function fields.
767 All fields with the same batch name will be computed by
768 a single function call. This changes the signature of the
771 .. _field-function-fnct: The ``fnct`` parameter
773 .. rubric:: The ``fnct`` parameter
775 The callable implementing the function field must have the following signature:
777 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
779 Implements the function field.
781 :param orm model: model to which the field belongs (should be ``self`` for
783 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
784 list of field names to compute.
785 :type field_name(s): str | [str]
786 :param arg: arbitrary value passed when declaring the function field
788 :return: mapping of ``ids`` to computed values, or if multi is provided,
789 to a map of field_names to computed values
791 The values in the returned dictionary must be of the type specified by the type
792 argument in the field declaration.
794 Here is an example with a simple function ``char`` function field::
797 def compute(self, cr, uid, ids, field_name, arg, context):
801 _columns['my_char'] = fields.function(compute, type='char', size=50)
803 # when called with ``ids=[1,2,3]``, ``compute`` could return:
807 3: False # null values should be returned explicitly too
810 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
811 of the field names that should be computed. Each value in the returned
812 dictionary must then be a dictionary mapping field names to values.
814 Here is an example where two function fields (``name`` and ``age``)
815 are both computed by a single function field::
818 def compute(self, cr, uid, ids, field_names, arg, context):
822 _columns['name'] = fields.function(compute_person_data, type='char',\
823 size=50, multi='person_data')
824 _columns[''age'] = fields.function(compute_person_data, type='integer',\
827 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
829 1: {'name': 'Bob', 'age': 23},
830 2: {'name': 'Sally', 'age': 19},
831 3: {'name': 'unknown', 'age': False}
834 .. _field-function-fnct-inv:
836 .. rubric:: The ``fnct_inv`` parameter
838 This callable implements the write operation for the function field
839 and must have the following signature:
841 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
843 Callable that implements the ``write`` operation for the function field.
845 :param orm model: model to which the field belongs (should be ``self`` for
847 :param int id: the identifier of the object to write on
848 :param str field_name: name of the field to set
849 :param fnct_inv_arg: arbitrary value passed when declaring the function field
852 When writing values for a function field, the ``multi`` parameter is ignored.
854 .. _field-function-fnct-search:
856 .. rubric:: The ``fnct_search`` parameter
858 This callable implements the search operation for the function field
859 and must have the following signature:
861 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
863 Callable that implements the ``search`` operation for the function field by expanding
864 a search criterion based on the function field into a new domain based only on
865 columns that are stored in the database.
867 :param orm model: model to which the field belongs (should be ``self`` for
869 :param orm model_again: same value as ``model`` (seriously! this is for backwards
871 :param str field_name: name of the field to search on
872 :param list criterion: domain component specifying the search criterion on the field.
874 :return: domain to use instead of ``criterion`` when performing the search.
875 This new domain must be based only on columns stored in the database, as it
876 will be used directly without any translation.
878 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
879 The most generic way to implement ``fnct_search`` is to directly search for the records that
880 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
881 ``[('id','in',[1,3,5])]``.
883 .. _field-function-store:
885 .. rubric:: The ``store`` parameter
887 The ``store`` parameter allows caching the result of the field computation in the
888 database, and defining the triggers that will invalidate that cache and force a
889 recomputation of the function field.
890 When not provided, the field is computed every time its value is read.
891 The value of ``store`` may be either ``True`` (to recompute the field value whenever
892 any field in the same record is modified), or a dictionary specifying a more
893 flexible set of recomputation triggers.
895 A trigger specification is a dictionary that maps the names of the models that
896 will trigger the computation, to a tuple describing the trigger rule, in the
900 'trigger_model': (mapping_function,
901 ['trigger_field1', 'trigger_field2'],
905 A trigger rule is defined by a 3-item tuple where:
907 * The ``mapping_function`` is defined as follows:
909 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
911 Callable that maps record ids of a trigger model to ids of the
912 corresponding records in the source model (whose field values
913 need to be recomputed).
915 :param orm model: trigger_model
916 :param list trigger_ids: ids of the records of trigger_model that were
919 :return: list of ids of the source model whose function field values
920 need to be recomputed
922 * The second item is a list of the fields who should act as triggers for
923 the computation. If an empty list is given, all fields will act as triggers.
924 * The last item is the priority, used to order the triggers when processing them
925 after any write operation on a model that has function field triggers. The
926 default priority is 10.
928 In fact, setting store = True is the same as using the following trigger dict::
931 'model_itself': (lambda self, cr, uid, ids, context: ids,
937 _classic_read = False
938 _classic_write = False
944 # multi: compute several fields in one call
946 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):
947 _column.__init__(self, **args)
950 self._fnct_inv = fnct_inv
953 if 'relation' in args:
954 self._obj = args['relation']
956 self.digits = args.get('digits', (16,2))
957 self.digits_compute = args.get('digits_compute', None)
959 self._fnct_inv_arg = fnct_inv_arg
963 self._fnct_search = fnct_search
966 if not fnct_search and not store:
967 self.selectable = False
970 if self._type != 'many2one':
971 # m2o fields need to return tuples with name_get, not just foreign keys
972 self._classic_read = True
973 self._classic_write = True
975 self._symbol_get=lambda x:x and str(x)
978 self._symbol_c = float._symbol_c
979 self._symbol_f = float._symbol_f
980 self._symbol_set = float._symbol_set
982 if type == 'boolean':
983 self._symbol_c = boolean._symbol_c
984 self._symbol_f = boolean._symbol_f
985 self._symbol_set = boolean._symbol_set
987 if type in ['integer','integer_big']:
988 self._symbol_c = integer._symbol_c
989 self._symbol_f = integer._symbol_f
990 self._symbol_set = integer._symbol_set
992 def digits_change(self, cr):
993 if self.digits_compute:
994 t = self.digits_compute(cr)
995 self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
999 def search(self, cr, uid, obj, name, args, context=None):
1000 if not self._fnct_search:
1001 #CHECKME: should raise an exception
1003 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1005 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1009 field_type = obj._columns[field]._type
1010 if field_type == "many2one":
1011 # make the result a tuple if it is not already one
1012 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1013 obj_model = obj.pool.get(obj._columns[field].relation)
1014 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1015 result = (value, dict_names[value])
1017 if field_type == 'binary':
1018 if context.get('bin_size'):
1019 # client requests only the size of binary fields
1020 result = get_nice_size(value)
1021 elif not context.get('bin_raw'):
1022 result = sanitize_binary_value(value)
1024 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1025 # integer/long values greater than 2^31-1 are not supported
1026 # in pure XMLRPC, so we have to pass them as floats :-(
1027 # This is not needed for stored fields and non-functional integer
1028 # fields, as their values are constrained by the database backend
1029 # to the same 32bits signed int limit.
1030 result = float(value)
1033 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1034 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1036 if self._multi and id in result:
1037 for field, value in result[id].iteritems():
1039 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1040 elif result.get(id):
1041 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1044 def set(self, cr, obj, id, name, value, user=None, context=None):
1048 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1050 # ---------------------------------------------------------
1052 # ---------------------------------------------------------
1054 class related(function):
1055 """Field that points to some data inside another field of the current record.
1060 'foo_id': fields.many2one('my.foo', 'Foo'),
1061 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1065 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1066 self._field_get2(cr, uid, obj, context)
1067 i = len(self._arg)-1
1070 if type(sarg) in [type([]), type( (1,) )]:
1071 where = [(self._arg[i], 'in', sarg)]
1073 where = [(self._arg[i], '=', sarg)]
1075 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1077 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1079 return [(self._arg[0], 'in', sarg)]
1081 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1082 self._field_get2(cr, uid, obj, context=context)
1083 if type(ids) != type([]):
1085 objlst = obj.browse(cr, uid, ids)
1089 for i in range(len(self.arg)):
1090 if not t_data: break
1091 field_detail = self._relations[i]
1092 if not t_data[self.arg[i]]:
1093 if self._type not in ('one2many', 'many2many'):
1096 elif field_detail['type'] in ('one2many', 'many2many'):
1097 if self._type != "many2one":
1099 t_data = t_data[self.arg[i]][0]
1104 t_data = t_data[self.arg[i]]
1106 model = obj.pool.get(self._relations[-1]['object'])
1107 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1109 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1110 self._field_get2(cr, uid, obj, context)
1111 if not ids: return {}
1112 relation = obj._name
1113 if self._type in ('one2many', 'many2many'):
1114 res = dict([(i, []) for i in ids])
1116 res = {}.fromkeys(ids, False)
1118 objlst = obj.browse(cr, 1, ids, context=context)
1123 relation = obj._name
1124 for i in range(len(self.arg)):
1125 field_detail = self._relations[i]
1126 relation = field_detail['object']
1128 if not t_data[self.arg[i]]:
1134 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1135 t_data = t_data[self.arg[i]][0]
1137 t_data = t_data[self.arg[i]]
1138 if type(t_data) == type(objlst[0]):
1139 res[data.id] = t_data.id
1141 res[data.id] = t_data
1142 if self._type=='many2one':
1143 ids = filter(None, res.values())
1145 # name_get as root, as seeing the name of a related
1146 # object depends on access right of source document,
1147 # not target, so user may not have access.
1148 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1151 res[r] = (res[r], ng[res[r]])
1152 elif self._type in ('one2many', 'many2many'):
1155 res[r] = [x.id for x in res[r]]
1158 def __init__(self, *arg, **args):
1160 self._relations = []
1161 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1162 if self.store is True:
1163 # TODO: improve here to change self.store = {...} according to related objects
1166 def _field_get2(self, cr, uid, obj, context=None):
1170 obj_name = obj._name
1171 for i in range(len(self._arg)):
1172 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1178 if f.get('relation',False):
1179 obj_name = f['relation']
1180 result[-1]['relation'] = f['relation']
1181 self._relations = result
1183 # ---------------------------------------------------------
1185 # ---------------------------------------------------------
1187 class dummy(function):
1188 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1191 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1194 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1197 def __init__(self, *arg, **args):
1199 self._relations = []
1200 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1202 # ---------------------------------------------------------
1204 # ---------------------------------------------------------
1205 class serialized(_column):
1206 def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
1207 self._serialize_func = serialize_func
1208 self._deserialize_func = deserialize_func
1210 self._symbol_set = (self._symbol_c, self._serialize_func)
1211 self._symbol_get = self._deserialize_func
1212 super(serialized, self).__init__(string=string, **args)
1214 # TODO: review completly this class for speed improvement
1215 class property(function):
1217 def _get_default(self, obj, cr, uid, prop_name, context=None):
1218 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1220 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1221 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1223 :param list of string prop_names: list of name of property fields for those we want the default value
1224 :return: map of property field names to their default value
1227 prop = obj.pool.get('ir.property')
1229 for prop_name in prop_names:
1230 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1233 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1234 prop = obj.pool.get('ir.property')
1235 vids = [obj._name + ',' + str(oid) for oid in ids]
1237 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1238 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1240 domain = [('res_id', 'in', vids)] + domain
1241 return prop.search(cr, uid, domain, context=context)
1243 # TODO: to rewrite more clean
1244 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1248 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1250 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1252 default_val = self._get_default(obj, cr, uid, prop_name, context)
1254 property_create = False
1255 if isinstance(default_val, openerp.osv.orm.browse_record):
1256 if default_val.id != id_val:
1257 property_create = True
1258 elif default_val != id_val:
1259 property_create = True
1262 def_id = self._field_get(cr, uid, obj._name, prop_name)
1263 company = obj.pool.get('res.company')
1264 cid = company._company_default_get(cr, uid, obj._name, def_id,
1266 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1268 prop = obj.pool.get('ir.property')
1269 return prop.create(cr, uid, {
1270 'name': propdef.name,
1272 'res_id': obj._name+','+str(id),
1274 'fields_id': def_id,
1279 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1280 prop = obj.pool.get('ir.property')
1281 # get the default values (for res_id = False) for the property fields
1282 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1284 # build the dictionary that will be returned
1287 res[id] = default_val.copy()
1289 for prop_name in prop_names:
1290 property_field = obj._all_columns.get(prop_name).column
1291 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1292 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1293 # in order to make a name_get in batch for all the ids needed.
1296 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1297 obj_reference = obj._name + ',' + str(id)
1298 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1300 res[id][prop_name] = value
1301 # Check existence as root (as seeing the name of a related
1302 # object depends on access right of source document,
1303 # not target, so user may not have access) in order to avoid
1304 # pointing on an unexisting record.
1305 if property_destination_obj:
1306 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1307 name_get_ids[id] = res[id][prop_name].id
1309 res[id][prop_name] = False
1310 if property_destination_obj:
1311 # name_get as root (as seeing the name of a related
1312 # object depends on access right of source document,
1313 # not target, so user may not have access.)
1314 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1315 # the property field is a m2o, we need to return a tuple with (id, name)
1316 for k, v in name_get_ids.iteritems():
1317 if res[k][prop_name]:
1318 res[k][prop_name] = (v , name_get_values.get(v))
1321 def _field_get(self, cr, uid, model_name, prop):
1322 if not self.field_id.get(cr.dbname):
1323 cr.execute('SELECT id \
1324 FROM ir_model_fields \
1325 WHERE name=%s AND model=%s', (prop, model_name))
1327 self.field_id[cr.dbname] = res and res[0]
1328 return self.field_id[cr.dbname]
1330 def __init__(self, obj_prop, **args):
1331 # TODO remove obj_prop parameter (use many2one type)
1333 function.__init__(self, self._fnct_read, False, self._fnct_write,
1334 obj_prop, multi='properties', **args)
1340 def field_to_dict(model, cr, user, field, context=None):
1341 """ Return a dictionary representation of a field.
1343 The string, help, and selection attributes (if any) are untranslated. This
1344 representation is the one returned by fields_get() (fields_get() will do
1349 res = {'type': field._type}
1350 # This additional attributes for M2M and function field is added
1351 # because we need to display tooltip with this additional information
1352 # when client is started in debug mode.
1353 if isinstance(field, function):
1354 res['function'] = field._fnct and field._fnct.func_name or False
1355 res['store'] = field.store
1356 if isinstance(field.store, dict):
1357 res['store'] = str(field.store)
1358 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1359 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1360 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1361 res['func_obj'] = field._obj or False
1362 if isinstance(field, many2many):
1363 (table, col1, col2) = field._sql_names(model)
1364 res['related_columns'] = [col1, col2]
1365 res['third_table'] = table
1366 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1367 'change_default', 'translate', 'help', 'select', 'selectable'):
1368 if getattr(field, arg):
1369 res[arg] = getattr(field, arg)
1370 for arg in ('digits', 'invisible', 'filters'):
1371 if getattr(field, arg, None):
1372 res[arg] = getattr(field, arg)
1375 res['string'] = field.string
1377 res['help'] = field.help
1379 if hasattr(field, 'selection'):
1380 if isinstance(field.selection, (tuple, list)):
1381 res['selection'] = field.selection
1383 # call the 'dynamic selection' function
1384 res['selection'] = field.selection(model, cr, user, context)
1385 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1386 res['relation'] = field._obj
1387 res['domain'] = field._domain
1388 res['context'] = field._context
1390 if isinstance(field, one2many):
1391 res['relation_field'] = field._fields_id
1396 class column_info(object):
1397 """Struct containing details about an osv column, either one local to
1398 its model, or one inherited via _inherits.
1400 :attr name: name of the column
1401 :attr column: column instance, subclass of osv.fields._column
1402 :attr parent_model: if the column is inherited, name of the model
1403 that contains it, None for local columns.
1404 :attr parent_column: the name of the column containing the m2o
1405 relationship to the parent model that contains
1406 this column, None for local columns.
1407 :attr original_parent: if the column is inherited, name of the original
1408 parent model that contains it i.e in case of multilevel
1409 inheritence, None for local columns.
1411 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1413 self.column = column
1414 self.parent_model = parent_model
1415 self.parent_column = parent_column
1416 self.original_parent = original_parent
1418 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: