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
41 from psycopg2 import Binary
44 import openerp.tools as tools
45 from openerp.tools.translate import _
46 from openerp.tools import float_round, float_repr
49 _logger = logging.getLogger(__name__)
51 def _symbol_set(symb):
52 if symb == None or symb == False:
54 elif isinstance(symb, unicode):
55 return symb.encode('utf-8')
59 class _column(object):
60 """ Base of all fields, a database column
62 An instance of this object is a *description* of a database column. It will
63 not hold any data, but only provide the methods to manipulate data of an
64 ORM record or even prepare/update the database to hold such a field of data.
74 _symbol_f = _symbol_set
75 _symbol_set = (_symbol_c, _symbol_f)
78 # used to hide a certain field type in the list of field types
81 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):
84 The 'manual' keyword argument specifies if the field is a custom one.
85 It corresponds to the 'state' column in ir_model_fields.
92 self.states = states or {}
94 self.readonly = readonly
95 self.required = required
97 self.help = args.get('help', '')
98 self.priority = priority
99 self.change_default = change_default
100 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
101 self.translate = translate
102 self._domain = domain
103 self._context = context
109 self.selectable = True
110 self.group_operator = args.get('group_operator', False)
113 setattr(self, a, args[a])
118 def set(self, cr, obj, id, name, value, user=None, context=None):
119 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
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]
130 # ---------------------------------------------------------
132 # ---------------------------------------------------------
133 class boolean(_column):
136 _symbol_f = lambda x: x and 'True' or 'False'
137 _symbol_set = (_symbol_c, _symbol_f)
139 def __init__(self, string='unknown', required=False, **args):
140 super(boolean, self).__init__(string=string, required=required, **args)
143 "required=True is deprecated: making a boolean field"
144 " `required` has no effect, as NULL values are "
145 "automatically turned into False.")
147 class integer(_column):
150 _symbol_f = lambda x: int(x or 0)
151 _symbol_set = (_symbol_c, _symbol_f)
152 _symbol_get = lambda self,x: x or 0
154 def __init__(self, string='unknown', required=False, **args):
155 super(integer, self).__init__(string=string, required=required, **args)
158 "required=True is deprecated: making an integer field"
159 " `required` has no effect, as NULL values are "
160 "automatically turned into 0.")
162 class integer_big(_column):
163 """Experimental 64 bit integer column type, currently unused.
165 TODO: this field should work fine for values up
166 to 32 bits, but greater values will not fit
167 in the XML-RPC int type, so a specific
168 get() method is needed to pass them as floats,
169 like what we do for integer functional fields.
171 _type = 'integer_big'
172 # do not reference the _symbol_* of integer class, as that would possibly
173 # unbind the lambda functions
175 _symbol_f = lambda x: int(x or 0)
176 _symbol_set = (_symbol_c, _symbol_f)
177 _symbol_get = lambda self,x: x or 0
180 def __init__(self, string='unknown', required=False, **args):
181 super(integer_big, self).__init__(string=string, required=required, **args)
184 "required=True is deprecated: making an integer_big field"
185 " `required` has no effect, as NULL values are "
186 "automatically turned into 0.")
188 class reference(_column):
190 _classic_read = False # post-process to handle missing target
192 def __init__(self, string, selection, size, **args):
193 _column.__init__(self, string=string, size=size, selection=selection, **args)
195 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
197 # copy initial values fetched previously.
199 result[value['id']] = value[name]
201 model, res_id = value[name].split(',')
202 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
203 result[value['id']] = False
209 def __init__(self, string, size, **args):
210 _column.__init__(self, string=string, size=size, **args)
211 self._symbol_set = (self._symbol_c, self._symbol_set_char)
213 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
214 def _symbol_set_char(self, symb):
216 # * we need to remove the "symb==False" from the next line BUT
217 # for now too many things rely on this broken behavior
218 # * the symb==None test should be common to all data types
219 if symb == None or symb == False:
222 # we need to convert the string to a unicode object to be able
223 # to evaluate its length (and possibly truncate it) reliably
224 u_symb = tools.ustr(symb)
226 return u_symb[:self.size].encode('utf8')
234 class float(_column):
237 _symbol_f = lambda x: __builtin__.float(x or 0.0)
238 _symbol_set = (_symbol_c, _symbol_f)
239 _symbol_get = lambda self,x: x or 0.0
241 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
242 _column.__init__(self, string=string, required=required, **args)
244 # synopsis: digits_compute(cr) -> (precision, scale)
245 self.digits_compute = digits_compute
248 "required=True is deprecated: making a float field"
249 " `required` has no effect, as NULL values are "
250 "automatically turned into 0.0.")
252 def digits_change(self, cr):
253 if self.digits_compute:
254 self.digits = self.digits_compute(cr)
256 precision, scale = self.digits
257 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
258 precision_digits=scale),
259 precision_digits=scale))
266 """ Returns the current date in a format fit for being a
267 default value to a ``date`` field.
269 This method should be provided as is to the _defaults dict, it
270 should not be called.
272 return DT.date.today().strftime(
273 tools.DEFAULT_SERVER_DATE_FORMAT)
276 def context_today(model, cr, uid, context=None, timestamp=None):
277 """Returns the current date as seen in the client's timezone
278 in a format fit for date fields.
279 This method may be passed as value to initialize _defaults.
281 :param Model model: model (osv) for which the date value is being
282 computed - technical field, currently ignored,
283 automatically passed when used in _defaults.
284 :param datetime timestamp: optional datetime value to use instead of
285 the current date and time (must be a
286 datetime, regular dates can't be converted
288 :param dict context: the 'tz' key in the context should give the
289 name of the User/Client timezone (otherwise
293 today = timestamp or DT.datetime.now()
295 if context and context.get('tz'):
297 utc = pytz.timezone('UTC')
298 context_tz = pytz.timezone(context['tz'])
299 utc_today = utc.localize(today, is_dst=False) # UTC = no DST
300 context_today = utc_today.astimezone(context_tz)
302 _logger.debug("failed to compute context/client-specific today date, "
303 "using the UTC value for `today`",
305 return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
307 class datetime(_column):
311 """ Returns the current datetime in a format fit for being a
312 default value to a ``datetime`` field.
314 This method should be provided as is to the _defaults dict, it
315 should not be called.
317 return DT.datetime.now().strftime(
318 tools.DEFAULT_SERVER_DATETIME_FORMAT)
321 def context_timestamp(cr, uid, timestamp, context=None):
322 """Returns the given timestamp converted to the client's timezone.
323 This method is *not* meant for use as a _defaults initializer,
324 because datetime fields are automatically converted upon
325 display on client side. For _defaults you :meth:`fields.datetime.now`
326 should be used instead.
328 :param datetime timestamp: naive datetime value (expressed in UTC)
329 to be converted to the client timezone
330 :param dict context: the 'tz' key in the context should give the
331 name of the User/Client timezone (otherwise
334 :return: timestamp converted to timezone-aware datetime in context
337 assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
338 if context and context.get('tz'):
340 utc = pytz.timezone('UTC')
341 context_tz = pytz.timezone(context['tz'])
342 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
343 return utc_timestamp.astimezone(context_tz)
345 _logger.debug("failed to compute context/client-specific timestamp, "
346 "using the UTC value",
355 """ Returns the current time in a format fit for being a
356 default value to a ``time`` field.
358 This method should be proivided as is to the _defaults dict,
359 it should not be called.
361 return DT.datetime.now().strftime(
362 tools.DEFAULT_SERVER_TIME_FORMAT)
364 class binary(_column):
367 _symbol_f = lambda symb: symb and Binary(symb) or None
368 _symbol_set = (_symbol_c, _symbol_f)
369 _symbol_get = lambda self, x: x and str(x)
371 _classic_read = False
374 def __init__(self, string='unknown', filters=None, **args):
375 _column.__init__(self, string=string, **args)
376 self.filters = filters
378 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
391 # If client is requesting only the size of the field, we return it instead
392 # of the content. Presumably a separate request will be done to read the actual
393 # content if it's needed at some point.
394 # TODO: after 6.0 we should consider returning a dict with size and content instead of
395 # having an implicit convention for the value
396 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
397 res[i] = tools.human_size(long(val))
402 class selection(_column):
405 def __init__(self, selection, string='unknown', **args):
406 _column.__init__(self, string=string, **args)
407 self.selection = selection
409 # ---------------------------------------------------------
411 # ---------------------------------------------------------
414 # Values: (0, 0, { fields }) create
415 # (1, ID, { fields }) update
416 # (2, ID) remove (delete)
417 # (3, ID) unlink one (target id or target of relation)
419 # (5) unlink all (only valid for one2many)
421 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
422 class one2one(_column):
423 _classic_read = False
424 _classic_write = True
428 def __init__(self, obj, string='unknown', **args):
429 _logger.warning("The one2one field is deprecated and doesn't work anymore.")
430 _column.__init__(self, string=string, **args)
433 def set(self, cr, obj_src, id, field, act, user=None, context=None):
436 obj = obj_src.pool.get(self._obj)
437 self._table = obj_src.pool.get(self._obj)._table
439 id_new = obj.create(cr, user, act[1])
440 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
442 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
443 id = cr.fetchone()[0]
444 obj.write(cr, user, [id], act[1], context=context)
446 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
447 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
450 class many2one(_column):
451 _classic_read = False
452 _classic_write = True
455 _symbol_f = lambda x: x or None
456 _symbol_set = (_symbol_c, _symbol_f)
458 def __init__(self, obj, string='unknown', **args):
459 _column.__init__(self, string=string, **args)
462 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
470 res[r['id']] = r[name]
472 res.setdefault(id, '')
473 obj = obj.pool.get(self._obj)
475 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
476 # we use uid=1 because the visibility of a many2one field value (just id and name)
477 # must be the access right of the parent form and not the linked object itself.
478 records = dict(obj.name_get(cr, 1,
479 list(set([x for x in res.values() if isinstance(x, (int,long))])),
482 if res[id] in records:
483 res[id] = (res[id], records[res[id]])
488 def set(self, cr, obj_src, id, field, values, user=None, context=None):
491 obj = obj_src.pool.get(self._obj)
492 self._table = obj_src.pool.get(self._obj)._table
493 if type(values) == type([]):
496 id_new = obj.create(cr, act[2])
497 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
499 obj.write(cr, [act[1]], act[2], context=context)
501 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
502 elif act[0] == 3 or act[0] == 5:
503 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
505 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
508 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
510 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
512 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
513 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
516 class one2many(_column):
517 _classic_read = False
518 _classic_write = False
522 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
523 _column.__init__(self, string=string, **args)
525 self._fields_id = fields_id
527 #one2many can't be used as condition for defaults
528 assert(self.change_default != True)
530 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
534 context = context.copy()
535 context.update(self._context)
543 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
544 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
545 if r[self._fields_id] in res:
546 res[r[self._fields_id]].append(r['id'])
549 def set(self, cr, obj, id, field, values, user=None, context=None):
554 context = context.copy()
555 context.update(self._context)
556 context['no_store_function'] = True
559 _table = obj.pool.get(self._obj)._table
560 obj = obj.pool.get(self._obj)
563 act[2][self._fields_id] = id
564 id_new = obj.create(cr, user, act[2], context=context)
565 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
567 obj.write(cr, user, [act[1]], act[2], context=context)
569 obj.unlink(cr, user, [act[1]], context=context)
571 reverse_rel = obj._all_columns.get(self._fields_id)
572 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
573 # if the model has on delete cascade, just delete the row
574 if reverse_rel.column.ondelete == "cascade":
575 obj.unlink(cr, user, [act[1]], context=context)
577 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
579 # Must use write() to recompute parent_store structure if needed
580 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
582 reverse_rel = obj._all_columns.get(self._fields_id)
583 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
584 # if the o2m has a static domain we must respect it when unlinking
585 extra_domain = self._domain if isinstance(getattr(self, '_domain', None), list) else []
586 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
587 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
588 # otherwise we only nullify the reverse foreign key column.
589 if reverse_rel.column.ondelete == "cascade":
590 obj.unlink(cr, user, ids_to_unlink, context=context)
592 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
594 # Must use write() to recompute parent_store structure if needed
595 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
597 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
598 ids3 = map(lambda x:x[0], cr.fetchall())
599 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
602 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
603 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
607 # Values: (0, 0, { fields }) create
608 # (1, ID, { fields }) update (write fields to ID)
609 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
610 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
611 # (4, ID) link (add a relationship)
613 # (6, ?, ids) set a list of links
615 class many2many(_column):
616 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
617 low-level details of the intermediary relationship table transparently.
618 A many-to-many relationship is always symmetrical, and can be declared and accessed
619 from either endpoint model.
620 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
621 or id2 (destination foreign key column name) are not specified, the system will
622 provide default values. This will by default only allow one single symmetrical
623 many-to-many relationship between the source and destination model.
624 For multiple many-to-many relationship between the same models and for
625 relationships where source and destination models are the same, ``rel``, ``id1``
626 and ``id2`` should be specified explicitly.
628 :param str obj: destination model
629 :param str rel: optional name of the intermediary relationship table. If not specified,
630 a canonical name will be derived based on the alphabetically-ordered
631 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
632 Automatic naming is not possible when the source and destination are
633 the same, for obvious ambiguity reasons.
634 :param str id1: optional name for the column holding the foreign key to the current
635 model in the relationship table. If not specified, a canonical name
636 will be derived based on the model name (in the form: `src_model_id`).
637 :param str id2: optional name for the column holding the foreign key to the destination
638 model in the relationship table. If not specified, a canonical name
639 will be derived based on the model name (in the form: `dest_model_id`)
640 :param str string: field label
642 _classic_read = False
643 _classic_write = False
647 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
650 _column.__init__(self, string=string, **args)
652 if rel and '.' in rel:
653 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
654 'You used %s, which is not a valid SQL table name.')% (string,rel))
660 def _sql_names(self, source_model):
661 """Return the SQL names defining the structure of the m2m relationship table
663 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
664 local_col is the name of the column holding the current model's FK, and
665 dest_col is the name of the column holding the destination model's FK, and
667 tbl, col1, col2 = self._rel, self._id1, self._id2
668 if not all((tbl, col1, col2)):
669 # the default table name is based on the stable alphabetical order of tables
670 dest_model = source_model.pool.get(self._obj)
671 tables = tuple(sorted([source_model._table, dest_model._table]))
673 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
674 'is not possible when source and destination models are '\
676 tbl = '%s_%s_rel' % tables
678 col1 = '%s_id' % source_model._table
680 col2 = '%s_id' % dest_model._table
681 return (tbl, col1, col2)
683 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
695 "Specifying offset at a many2many.get() is deprecated and may"
696 " produce unpredictable results.")
697 obj = model.pool.get(self._obj)
698 rel, id1, id2 = self._sql_names(model)
700 # static domains are lists, and are evaluated both here and on client-side, while string
701 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
702 # FIXME: make this distinction explicit in API!
703 domain = isinstance(self._domain, list) and self._domain or []
705 wquery = obj._where_calc(cr, user, domain, context=context)
706 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
707 from_c, where_c, where_params = wquery.get_sql()
709 where_c = ' AND ' + where_c
711 if offset or self._limit:
712 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
717 if self._limit is not None:
718 limit_str = ' LIMIT %d' % self._limit
720 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
721 FROM %(rel)s, %(from_c)s \
722 WHERE %(rel)s.%(id1)s IN %%s \
723 AND %(rel)s.%(id2)s = %(tbl)s.id \
735 'order_by': order_by,
738 cr.execute(query, [tuple(ids),] + where_params)
739 for r in cr.fetchall():
740 res[r[1]].append(r[0])
743 def set(self, cr, model, id, name, values, user=None, context=None):
748 rel, id1, id2 = self._sql_names(model)
749 obj = model.pool.get(self._obj)
751 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
754 idnew = obj.create(cr, user, act[2], context=context)
755 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
757 obj.write(cr, user, [act[1]], act[2], context=context)
759 obj.unlink(cr, user, [act[1]], context=context)
761 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
763 # following queries are in the same transaction - so should be relatively safe
764 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
765 if not cr.fetchone():
766 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
768 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
771 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
773 d1 = ' and ' + ' and '.join(d1)
776 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)
778 for act_nbr in act[2]:
779 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
782 # TODO: use a name_search
784 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
785 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
788 def get_nice_size(value):
790 if isinstance(value, (int,long)):
792 elif value: # this is supposed to be a string
794 return tools.human_size(size)
796 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
797 # and http://bugs.python.org/issue10066
798 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
800 def sanitize_binary_value(value):
801 # binary fields should be 7-bit ASCII base64-encoded data,
802 # but we do additional sanity checks to make sure the values
803 # are not something else that won't pass via XML-RPC
804 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
805 # these builtin types are meant to pass untouched
808 # Handle invalid bytes values that will cause problems
809 # for XML-RPC. See for more info:
810 # - http://bugs.python.org/issue10066
811 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
813 # Coercing to unicode would normally allow it to properly pass via
814 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
815 # (this works for _any_ byte values, thanks to the fallback
816 # to latin-1 passthrough encoding when decoding to unicode)
817 value = tools.ustr(value)
819 # Due to Python bug #10066 this could still yield invalid XML
820 # bytes, specifically in the low byte range, that will crash
821 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
822 # So check for low bytes values, and if any, perform
823 # base64 encoding - not very smart or useful, but this is
824 # our last resort to avoid crashing the request.
825 if invalid_xml_low_bytes.search(value):
826 # b64-encode after restoring the pure bytes with latin-1
827 # passthrough encoding
828 value = base64.b64encode(value.encode('latin-1'))
833 # ---------------------------------------------------------
835 # ---------------------------------------------------------
836 class function(_column):
838 A field whose value is computed by a function (rather
839 than being read from the database).
841 :param fnct: the callable that will compute the field value.
842 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
843 :param fnct_inv: the callable that will allow writing values in that field
844 (if not provided, the field is read-only).
845 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
847 :param str type: type of the field simulated by the function field
848 :param fnct_search: the callable that allows searching on the field
849 (if not provided, search will not return any result).
850 :param store: store computed value in database
851 (see :ref:`The *store* parameter <field-function-store>`).
852 :type store: True or dict specifying triggers for field computation
853 :param multi: name of batch for batch computation of function fields.
854 All fields with the same batch name will be computed by
855 a single function call. This changes the signature of the
858 .. _field-function-fnct: The ``fnct`` parameter
860 .. rubric:: The ``fnct`` parameter
862 The callable implementing the function field must have the following signature:
864 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
866 Implements the function field.
868 :param orm model: model to which the field belongs (should be ``self`` for
870 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
871 list of field names to compute.
872 :type field_name(s): str | [str]
873 :param arg: arbitrary value passed when declaring the function field
875 :return: mapping of ``ids`` to computed values, or if multi is provided,
876 to a map of field_names to computed values
878 The values in the returned dictionary must be of the type specified by the type
879 argument in the field declaration.
881 Here is an example with a simple function ``char`` function field::
884 def compute(self, cr, uid, ids, field_name, arg, context):
888 _columns['my_char'] = fields.function(compute, type='char', size=50)
890 # when called with ``ids=[1,2,3]``, ``compute`` could return:
894 3: False # null values should be returned explicitly too
897 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
898 of the field names that should be computed. Each value in the returned
899 dictionary must then be a dictionary mapping field names to values.
901 Here is an example where two function fields (``name`` and ``age``)
902 are both computed by a single function field::
905 def compute(self, cr, uid, ids, field_names, arg, context):
909 _columns['name'] = fields.function(compute_person_data, type='char',\
910 size=50, multi='person_data')
911 _columns[''age'] = fields.function(compute_person_data, type='integer',\
914 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
916 1: {'name': 'Bob', 'age': 23},
917 2: {'name': 'Sally', 'age': 19},
918 3: {'name': 'unknown', 'age': False}
921 .. _field-function-fnct-inv:
923 .. rubric:: The ``fnct_inv`` parameter
925 This callable implements the write operation for the function field
926 and must have the following signature:
928 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
930 Callable that implements the ``write`` operation for the function field.
932 :param orm model: model to which the field belongs (should be ``self`` for
934 :param int id: the identifier of the object to write on
935 :param str field_name: name of the field to set
936 :param fnct_inv_arg: arbitrary value passed when declaring the function field
939 When writing values for a function field, the ``multi`` parameter is ignored.
941 .. _field-function-fnct-search:
943 .. rubric:: The ``fnct_search`` parameter
945 This callable implements the search operation for the function field
946 and must have the following signature:
948 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
950 Callable that implements the ``search`` operation for the function field by expanding
951 a search criterion based on the function field into a new domain based only on
952 columns that are stored in the database.
954 :param orm model: model to which the field belongs (should be ``self`` for
956 :param orm model_again: same value as ``model`` (seriously! this is for backwards
958 :param str field_name: name of the field to search on
959 :param list criterion: domain component specifying the search criterion on the field.
961 :return: domain to use instead of ``criterion`` when performing the search.
962 This new domain must be based only on columns stored in the database, as it
963 will be used directly without any translation.
965 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
966 The most generic way to implement ``fnct_search`` is to directly search for the records that
967 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
968 ``[('id','in',[1,3,5])]``.
970 .. _field-function-store:
972 .. rubric:: The ``store`` parameter
974 The ``store`` parameter allows caching the result of the field computation in the
975 database, and defining the triggers that will invalidate that cache and force a
976 recomputation of the function field.
977 When not provided, the field is computed every time its value is read.
978 The value of ``store`` may be either ``True`` (to recompute the field value whenever
979 any field in the same record is modified), or a dictionary specifying a more
980 flexible set of recomputation triggers.
982 A trigger specification is a dictionary that maps the names of the models that
983 will trigger the computation, to a tuple describing the trigger rule, in the
987 'trigger_model': (mapping_function,
988 ['trigger_field1', 'trigger_field2'],
992 A trigger rule is defined by a 3-item tuple where:
994 * The ``mapping_function`` is defined as follows:
996 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
998 Callable that maps record ids of a trigger model to ids of the
999 corresponding records in the source model (whose field values
1000 need to be recomputed).
1002 :param orm model: trigger_model
1003 :param list trigger_ids: ids of the records of trigger_model that were
1006 :return: list of ids of the source model whose function field values
1007 need to be recomputed
1009 * The second item is a list of the fields who should act as triggers for
1010 the computation. If an empty list is given, all fields will act as triggers.
1011 * The last item is the priority, used to order the triggers when processing them
1012 after any write operation on a model that has function field triggers. The
1013 default priority is 10.
1015 In fact, setting store = True is the same as using the following trigger dict::
1018 'model_itself': (lambda self, cr, uid, ids, context: ids,
1024 _classic_read = False
1025 _classic_write = False
1031 # multi: compute several fields in one call
1033 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):
1034 _column.__init__(self, **args)
1037 self._fnct_inv = fnct_inv
1040 if 'relation' in args:
1041 self._obj = args['relation']
1043 self.digits = args.get('digits', (16,2))
1044 self.digits_compute = args.get('digits_compute', None)
1046 self._fnct_inv_arg = fnct_inv_arg
1050 self._fnct_search = fnct_search
1053 if not fnct_search and not store:
1054 self.selectable = False
1057 if self._type != 'many2one':
1058 # m2o fields need to return tuples with name_get, not just foreign keys
1059 self._classic_read = True
1060 self._classic_write = True
1062 self._symbol_get=lambda x:x and str(x)
1065 self._symbol_c = float._symbol_c
1066 self._symbol_f = float._symbol_f
1067 self._symbol_set = float._symbol_set
1069 if type == 'boolean':
1070 self._symbol_c = boolean._symbol_c
1071 self._symbol_f = boolean._symbol_f
1072 self._symbol_set = boolean._symbol_set
1074 if type in ['integer','integer_big']:
1075 self._symbol_c = integer._symbol_c
1076 self._symbol_f = integer._symbol_f
1077 self._symbol_set = integer._symbol_set
1079 def digits_change(self, cr):
1080 if self._type == 'float':
1081 if self.digits_compute:
1082 self.digits = self.digits_compute(cr)
1084 precision, scale = self.digits
1085 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1086 precision_digits=scale),
1087 precision_digits=scale))
1089 def search(self, cr, uid, obj, name, args, context=None):
1090 if not self._fnct_search:
1091 #CHECKME: should raise an exception
1093 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1095 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1099 field_type = obj._columns[field]._type
1100 if field_type == "many2one":
1101 # make the result a tuple if it is not already one
1102 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1103 obj_model = obj.pool.get(obj._columns[field].relation)
1104 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1105 result = (value, dict_names[value])
1107 if field_type == 'binary':
1108 if context.get('bin_size'):
1109 # client requests only the size of binary fields
1110 result = get_nice_size(value)
1111 elif not context.get('bin_raw'):
1112 result = sanitize_binary_value(value)
1114 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1115 # integer/long values greater than 2^31-1 are not supported
1116 # in pure XMLRPC, so we have to pass them as floats :-(
1117 # This is not needed for stored fields and non-functional integer
1118 # fields, as their values are constrained by the database backend
1119 # to the same 32bits signed int limit.
1120 result = float(value)
1123 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1124 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1126 if self._multi and id in result:
1127 for field, value in result[id].iteritems():
1129 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1130 elif result.get(id):
1131 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1134 def set(self, cr, obj, id, name, value, user=None, context=None):
1138 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1140 # ---------------------------------------------------------
1142 # ---------------------------------------------------------
1144 class related(function):
1145 """Field that points to some data inside another field of the current record.
1150 'foo_id': fields.many2one('my.foo', 'Foo'),
1151 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1155 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1156 self._field_get2(cr, uid, obj, context)
1157 i = len(self._arg)-1
1160 if type(sarg) in [type([]), type( (1,) )]:
1161 where = [(self._arg[i], 'in', sarg)]
1163 where = [(self._arg[i], '=', sarg)]
1165 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1167 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1169 return [(self._arg[0], 'in', sarg)]
1171 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1172 self._field_get2(cr, uid, obj, context=context)
1173 if type(ids) != type([]):
1175 objlst = obj.browse(cr, uid, ids)
1179 for i in range(len(self.arg)):
1180 if not t_data: break
1181 field_detail = self._relations[i]
1182 if not t_data[self.arg[i]]:
1183 if self._type not in ('one2many', 'many2many'):
1186 elif field_detail['type'] in ('one2many', 'many2many'):
1187 if self._type != "many2one":
1189 t_data = t_data[self.arg[i]][0]
1194 t_data = t_data[self.arg[i]]
1196 model = obj.pool.get(self._relations[-1]['object'])
1197 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1199 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1200 self._field_get2(cr, uid, obj, context)
1201 if not ids: return {}
1202 relation = obj._name
1203 if self._type in ('one2many', 'many2many'):
1204 res = dict([(i, []) for i in ids])
1206 res = {}.fromkeys(ids, False)
1208 objlst = obj.browse(cr, 1, ids, context=context)
1213 relation = obj._name
1214 for i in range(len(self.arg)):
1215 field_detail = self._relations[i]
1216 relation = field_detail['object']
1218 if not t_data[self.arg[i]]:
1224 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1225 t_data = t_data[self.arg[i]][0]
1227 t_data = t_data[self.arg[i]]
1228 if type(t_data) == type(objlst[0]):
1229 res[data.id] = t_data.id
1231 res[data.id] = t_data
1232 if self._type=='many2one':
1233 ids = filter(None, res.values())
1235 # name_get as root, as seeing the name of a related
1236 # object depends on access right of source document,
1237 # not target, so user may not have access.
1238 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1241 res[r] = (res[r], ng[res[r]])
1242 elif self._type in ('one2many', 'many2many'):
1245 res[r] = [x.id for x in res[r]]
1248 def __init__(self, *arg, **args):
1250 self._relations = []
1251 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1252 if self.store is True:
1253 # TODO: improve here to change self.store = {...} according to related objects
1256 def _field_get2(self, cr, uid, obj, context=None):
1260 obj_name = obj._name
1261 for i in range(len(self._arg)):
1262 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1268 if f.get('relation',False):
1269 obj_name = f['relation']
1270 result[-1]['relation'] = f['relation']
1271 self._relations = result
1274 class sparse(function):
1276 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1278 + For a many2many field, a list of tuples is expected.
1279 Here is the list of tuple that are accepted, with the corresponding semantics ::
1281 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1282 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1283 (2, ID) remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
1284 (3, ID) cut the link to the linked record with id = ID (delete the relationship between the two objects but does not delete the target object itself)
1285 (4, ID) link to existing record with id = ID (adds a relationship)
1286 (5) unlink all (like using (3,ID) for all linked records)
1287 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1290 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1292 + For a one2many field, a lits of tuples is expected.
1293 Here is the list of tuple that are accepted, with the corresponding semantics ::
1295 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1296 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1297 (2, ID) remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
1300 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1303 if self._type == 'many2many':
1304 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1307 elif self._type == 'one2many':
1310 relation_obj = obj.pool.get(self.relation)
1312 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1314 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1316 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1318 relation_obj.unlink(cr, uid, vals[1], context=context)
1319 read_value.remove(vals[1])
1324 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1325 if not type(ids) == list:
1327 records = obj.browse(cr, uid, ids, context=context)
1328 for record in records:
1329 # grab serialized value as object - already deserialized
1330 serialized = getattr(record, self.serialization_field)
1332 # simply delete the key to unset it.
1333 serialized.pop(field_name, None)
1335 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1336 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1339 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1341 records = obj.browse(cr, uid, ids, context=context)
1342 for record in records:
1343 # grab serialized value as object - already deserialized
1344 serialized = getattr(record, self.serialization_field)
1345 results[record.id] = {}
1346 for field_name in field_names:
1347 field_type = obj._columns[field_name]._type
1348 value = serialized.get(field_name, False)
1349 if field_type in ('one2many','many2many'):
1352 # filter out deleted records as superuser
1353 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1354 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1355 if type(value) in (int,long) and field_type == 'many2one':
1356 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1357 # check for deleted record as superuser
1358 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1360 results[record.id][field_name] = value
1363 def __init__(self, serialization_field, **kwargs):
1364 self.serialization_field = serialization_field
1365 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1369 # ---------------------------------------------------------
1371 # ---------------------------------------------------------
1373 class dummy(function):
1374 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1377 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1380 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1383 def __init__(self, *arg, **args):
1385 self._relations = []
1386 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1388 # ---------------------------------------------------------
1390 # ---------------------------------------------------------
1392 class serialized(_column):
1393 """ A field able to store an arbitrary python data structure.
1395 Note: only plain components allowed.
1398 def _symbol_set_struct(val):
1399 return simplejson.dumps(val)
1401 def _symbol_get_struct(self, val):
1402 return simplejson.loads(val or '{}')
1405 _type = 'serialized'
1408 _symbol_f = _symbol_set_struct
1409 _symbol_set = (_symbol_c, _symbol_f)
1410 _symbol_get = _symbol_get_struct
1412 # TODO: review completly this class for speed improvement
1413 class property(function):
1415 def _get_default(self, obj, cr, uid, prop_name, context=None):
1416 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1418 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1419 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1421 :param list of string prop_names: list of name of property fields for those we want the default value
1422 :return: map of property field names to their default value
1425 prop = obj.pool.get('ir.property')
1427 for prop_name in prop_names:
1428 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1431 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1432 prop = obj.pool.get('ir.property')
1433 vids = [obj._name + ',' + str(oid) for oid in ids]
1435 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1436 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1438 domain = [('res_id', 'in', vids)] + domain
1439 return prop.search(cr, uid, domain, context=context)
1441 # TODO: to rewrite more clean
1442 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1446 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1448 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1450 default_val = self._get_default(obj, cr, uid, prop_name, context)
1452 property_create = False
1453 if isinstance(default_val, openerp.osv.orm.browse_record):
1454 if default_val.id != id_val:
1455 property_create = True
1456 elif default_val != id_val:
1457 property_create = True
1460 def_id = self._field_get(cr, uid, obj._name, prop_name)
1461 company = obj.pool.get('res.company')
1462 cid = company._company_default_get(cr, uid, obj._name, def_id,
1464 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1466 prop = obj.pool.get('ir.property')
1467 return prop.create(cr, uid, {
1468 'name': propdef.name,
1470 'res_id': obj._name+','+str(id),
1472 'fields_id': def_id,
1477 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1478 prop = obj.pool.get('ir.property')
1479 # get the default values (for res_id = False) for the property fields
1480 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1482 # build the dictionary that will be returned
1485 res[id] = default_val.copy()
1487 for prop_name in prop_names:
1488 property_field = obj._all_columns.get(prop_name).column
1489 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1490 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1491 # in order to make a name_get in batch for all the ids needed.
1494 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1495 obj_reference = obj._name + ',' + str(id)
1496 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1498 res[id][prop_name] = value
1499 # Check existence as root (as seeing the name of a related
1500 # object depends on access right of source document,
1501 # not target, so user may not have access) in order to avoid
1502 # pointing on an unexisting record.
1503 if property_destination_obj:
1504 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1505 name_get_ids[id] = res[id][prop_name].id
1507 res[id][prop_name] = False
1508 if property_destination_obj:
1509 # name_get as root (as seeing the name of a related
1510 # object depends on access right of source document,
1511 # not target, so user may not have access.)
1512 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1513 # the property field is a m2o, we need to return a tuple with (id, name)
1514 for k, v in name_get_ids.iteritems():
1515 if res[k][prop_name]:
1516 res[k][prop_name] = (v , name_get_values.get(v))
1519 def _field_get(self, cr, uid, model_name, prop):
1520 if not self.field_id.get(cr.dbname):
1521 cr.execute('SELECT id \
1522 FROM ir_model_fields \
1523 WHERE name=%s AND model=%s', (prop, model_name))
1525 self.field_id[cr.dbname] = res and res[0]
1526 return self.field_id[cr.dbname]
1528 def __init__(self, obj_prop, **args):
1529 # TODO remove obj_prop parameter (use many2one type)
1531 function.__init__(self, self._fnct_read, False, self._fnct_write,
1532 obj_prop, multi='properties', **args)
1538 def field_to_dict(model, cr, user, field, context=None):
1539 """ Return a dictionary representation of a field.
1541 The string, help, and selection attributes (if any) are untranslated. This
1542 representation is the one returned by fields_get() (fields_get() will do
1547 res = {'type': field._type}
1548 # This additional attributes for M2M and function field is added
1549 # because we need to display tooltip with this additional information
1550 # when client is started in debug mode.
1551 if isinstance(field, function):
1552 res['function'] = field._fnct and field._fnct.func_name or False
1553 res['store'] = field.store
1554 if isinstance(field.store, dict):
1555 res['store'] = str(field.store)
1556 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1557 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1558 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1559 res['func_obj'] = field._obj or False
1560 if isinstance(field, many2many):
1561 (table, col1, col2) = field._sql_names(model)
1562 res['related_columns'] = [col1, col2]
1563 res['third_table'] = table
1564 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1565 'change_default', 'translate', 'help', 'select', 'selectable'):
1566 if getattr(field, arg):
1567 res[arg] = getattr(field, arg)
1568 for arg in ('digits', 'invisible', 'filters'):
1569 if getattr(field, arg, None):
1570 res[arg] = getattr(field, arg)
1573 res['string'] = field.string
1575 res['help'] = field.help
1577 if hasattr(field, 'selection'):
1578 if isinstance(field.selection, (tuple, list)):
1579 res['selection'] = field.selection
1581 # call the 'dynamic selection' function
1582 res['selection'] = field.selection(model, cr, user, context)
1583 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1584 res['relation'] = field._obj
1585 res['domain'] = field._domain
1586 res['context'] = field._context
1588 if isinstance(field, one2many):
1589 res['relation_field'] = field._fields_id
1594 class column_info(object):
1595 """Struct containing details about an osv column, either one local to
1596 its model, or one inherited via _inherits.
1598 :attr name: name of the column
1599 :attr column: column instance, subclass of osv.fields._column
1600 :attr parent_model: if the column is inherited, name of the model
1601 that contains it, None for local columns.
1602 :attr parent_column: the name of the column containing the m2o
1603 relationship to the parent model that contains
1604 this column, None for local columns.
1605 :attr original_parent: if the column is inherited, name of the original
1606 parent model that contains it i.e in case of multilevel
1607 inheritence, None for local columns.
1609 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1611 self.column = column
1612 self.parent_model = parent_model
1613 self.parent_column = parent_column
1614 self.original_parent = original_parent
1616 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: