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
30 * _auto_join: for one2many and many2one fields, tells whether select
31 queries will join the relational table instead of replacing the
32 field condition by an equivalent-one based on a search.
44 from psycopg2 import Binary
47 import openerp.tools as tools
48 from openerp.tools.translate import _
49 from openerp.tools import float_round, float_repr
50 from openerp.tools import html_sanitize
52 from openerp import SUPERUSER_ID
54 _logger = logging.getLogger(__name__)
56 def _symbol_set(symb):
57 if symb is None or symb == False:
59 elif isinstance(symb, unicode):
60 return symb.encode('utf-8')
64 class _column(object):
65 """ Base of all fields, a database column
67 An instance of this object is a *description* of a database column. It will
68 not hold any data, but only provide the methods to manipulate data of an
69 ORM record or even prepare/update the database to hold such a field of data.
80 _symbol_f = _symbol_set
81 _symbol_set = (_symbol_c, _symbol_f)
84 # used to hide a certain field type in the list of field types
87 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):
90 The 'manual' keyword argument specifies if the field is a custom one.
91 It corresponds to the 'state' column in ir_model_fields.
98 self.states = states or {}
100 self.readonly = readonly
101 self.required = required
103 self.help = args.get('help', '')
104 self.priority = priority
105 self.change_default = change_default
106 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
107 self.translate = translate
108 self._domain = domain
109 self._context = context
115 self.selectable = True
116 self.group_operator = args.get('group_operator', False)
117 self.groups = False # CSV list of ext IDs of groups that can access this field
118 self.deprecated = False # Optional deprecation warning
121 setattr(self, a, args[a])
126 def set(self, cr, obj, id, name, value, user=None, context=None):
127 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
129 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
130 raise Exception(_('undefined get method !'))
132 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
133 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
134 res = obj.read(cr, uid, ids, [name], context=context)
135 return [x[name] for x in res]
137 def as_display_name(self, cr, uid, obj, value, context=None):
138 """Converts a field value to a suitable string representation for a record,
139 e.g. when this field is used as ``rec_name``.
141 :param obj: the ``BaseModel`` instance this column belongs to
142 :param value: a proper value as returned by :py:meth:`~openerp.orm.osv.BaseModel.read`
145 # delegated to class method, so a column type A can delegate
146 # to a column type B.
147 return self._as_display_name(self, cr, uid, obj, value, context=None)
150 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
151 # This needs to be a class method, in case a column type A as to delegate
152 # to a column type B.
153 return tools.ustr(value)
155 # ---------------------------------------------------------
157 # ---------------------------------------------------------
158 class boolean(_column):
161 _symbol_f = lambda x: x and 'True' or 'False'
162 _symbol_set = (_symbol_c, _symbol_f)
164 def __init__(self, string='unknown', required=False, **args):
165 super(boolean, self).__init__(string=string, required=required, **args)
168 "required=True is deprecated: making a boolean field"
169 " `required` has no effect, as NULL values are "
170 "automatically turned into False. args: %r",args)
172 class integer(_column):
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
179 def __init__(self, string='unknown', required=False, **args):
180 super(integer, self).__init__(string=string, required=required, **args)
182 class reference(_column):
184 _classic_read = False # post-process to handle missing target
186 def __init__(self, string, selection, size, **args):
187 _column.__init__(self, string=string, size=size, selection=selection, **args)
189 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
191 # copy initial values fetched previously.
193 result[value['id']] = value[name]
195 model, res_id = value[name].split(',')
196 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
197 result[value['id']] = False
201 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
203 # reference fields have a 'model,id'-like value, that we need to convert
205 model_name, res_id = value.split(',')
206 model = obj.pool.get(model_name)
208 return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
209 return tools.ustr(value)
214 def __init__(self, string="unknown", size=None, **args):
215 _column.__init__(self, string=string, size=size or None, **args)
216 self._symbol_set = (self._symbol_c, self._symbol_set_char)
218 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
219 def _symbol_set_char(self, symb):
221 # * we need to remove the "symb==False" from the next line BUT
222 # for now too many things rely on this broken behavior
223 # * the symb==None test should be common to all data types
224 if symb is None or symb == False:
227 # we need to convert the string to a unicode object to be able
228 # to evaluate its length (and possibly truncate it) reliably
229 u_symb = tools.ustr(symb)
231 return u_symb[:self.size].encode('utf8')
241 if x is None or x == False:
243 return html_sanitize(x)
245 _symbol_set = (_symbol_c, _symbol_f)
249 class float(_column):
252 _symbol_f = lambda x: __builtin__.float(x or 0.0)
253 _symbol_set = (_symbol_c, _symbol_f)
254 _symbol_get = lambda self,x: x or 0.0
256 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
257 _column.__init__(self, string=string, required=required, **args)
259 # synopsis: digits_compute(cr) -> (precision, scale)
260 self.digits_compute = digits_compute
262 def digits_change(self, cr):
263 if self.digits_compute:
264 self.digits = self.digits_compute(cr)
266 precision, scale = self.digits
267 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
268 precision_digits=scale),
269 precision_digits=scale))
276 """ Returns the current date in a format fit for being a
277 default value to a ``date`` field.
279 This method should be provided as is to the _defaults dict, it
280 should not be called.
282 return DT.date.today().strftime(
283 tools.DEFAULT_SERVER_DATE_FORMAT)
286 def context_today(model, cr, uid, context=None, timestamp=None):
287 """Returns the current date as seen in the client's timezone
288 in a format fit for date fields.
289 This method may be passed as value to initialize _defaults.
291 :param Model model: model (osv) for which the date value is being
292 computed - technical field, currently ignored,
293 automatically passed when used in _defaults.
294 :param datetime timestamp: optional datetime value to use instead of
295 the current date and time (must be a
296 datetime, regular dates can't be converted
298 :param dict context: the 'tz' key in the context should give the
299 name of the User/Client timezone (otherwise
303 today = timestamp or DT.datetime.now()
305 if context and context.get('tz'):
307 utc = pytz.timezone('UTC')
308 context_tz = pytz.timezone(context['tz'])
309 utc_today = utc.localize(today, is_dst=False) # UTC = no DST
310 context_today = utc_today.astimezone(context_tz)
312 _logger.debug("failed to compute context/client-specific today date, "
313 "using the UTC value for `today`",
315 return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
317 class datetime(_column):
321 """ Returns the current datetime in a format fit for being a
322 default value to a ``datetime`` field.
324 This method should be provided as is to the _defaults dict, it
325 should not be called.
327 return DT.datetime.now().strftime(
328 tools.DEFAULT_SERVER_DATETIME_FORMAT)
331 def context_timestamp(cr, uid, timestamp, context=None):
332 """Returns the given timestamp converted to the client's timezone.
333 This method is *not* meant for use as a _defaults initializer,
334 because datetime fields are automatically converted upon
335 display on client side. For _defaults you :meth:`fields.datetime.now`
336 should be used instead.
338 :param datetime timestamp: naive datetime value (expressed in UTC)
339 to be converted to the client timezone
340 :param dict context: the 'tz' key in the context should give the
341 name of the User/Client timezone (otherwise
344 :return: timestamp converted to timezone-aware datetime in context
347 assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
348 if context and context.get('tz'):
350 utc = pytz.timezone('UTC')
351 context_tz = pytz.timezone(context['tz'])
352 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
353 return utc_timestamp.astimezone(context_tz)
355 _logger.debug("failed to compute context/client-specific timestamp, "
356 "using the UTC value",
360 class binary(_column):
364 # Binary values may be byte strings (python 2.6 byte array), but
365 # the legacy OpenERP convention is to transfer and store binaries
366 # as base64-encoded strings. The base64 string may be provided as a
367 # unicode in some circumstances, hence the str() cast in symbol_f.
368 # This str coercion will only work for pure ASCII unicode strings,
369 # on purpose - non base64 data must be passed as a 8bit byte strings.
370 _symbol_f = lambda symb: symb and Binary(str(symb)) or None
372 _symbol_set = (_symbol_c, _symbol_f)
373 _symbol_get = lambda self, x: x and str(x)
375 _classic_read = False
378 def __init__(self, string='unknown', filters=None, **args):
379 _column.__init__(self, string=string, **args)
380 self.filters = filters
382 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
395 # If client is requesting only the size of the field, we return it instead
396 # of the content. Presumably a separate request will be done to read the actual
397 # content if it's needed at some point.
398 # TODO: after 6.0 we should consider returning a dict with size and content instead of
399 # having an implicit convention for the value
400 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
401 res[i] = tools.human_size(long(val))
406 class selection(_column):
409 def __init__(self, selection, string='unknown', **args):
410 _column.__init__(self, string=string, **args)
411 self.selection = selection
413 # ---------------------------------------------------------
415 # ---------------------------------------------------------
418 # Values: (0, 0, { fields }) create
419 # (1, ID, { fields }) update
420 # (2, ID) remove (delete)
421 # (3, ID) unlink one (target id or target of relation)
423 # (5) unlink all (only valid for one2many)
426 class many2one(_column):
427 _classic_read = False
428 _classic_write = True
431 _symbol_f = lambda x: x or None
432 _symbol_set = (_symbol_c, _symbol_f)
434 def __init__(self, obj, string='unknown', auto_join=False, **args):
435 _column.__init__(self, string=string, **args)
437 self._auto_join = auto_join
439 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
447 res[r['id']] = r[name]
449 res.setdefault(id, '')
450 obj = obj.pool.get(self._obj)
452 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
453 # we use uid=1 because the visibility of a many2one field value (just id and name)
454 # must be the access right of the parent form and not the linked object itself.
455 records = dict(obj.name_get(cr, SUPERUSER_ID,
456 list(set([x for x in res.values() if isinstance(x, (int,long))])),
459 if res[id] in records:
460 res[id] = (res[id], records[res[id]])
465 def set(self, cr, obj_src, id, field, values, user=None, context=None):
468 obj = obj_src.pool.get(self._obj)
469 self._table = obj_src.pool.get(self._obj)._table
470 if type(values) == type([]):
473 id_new = obj.create(cr, act[2])
474 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
476 obj.write(cr, [act[1]], act[2], context=context)
478 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
479 elif act[0] == 3 or act[0] == 5:
480 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
482 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
485 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
487 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
489 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
490 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
494 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
495 return value[1] if isinstance(value, tuple) else tools.ustr(value)
498 class one2many(_column):
499 _classic_read = False
500 _classic_write = False
504 def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args):
505 _column.__init__(self, string=string, **args)
507 self._fields_id = fields_id
509 self._auto_join = auto_join
510 #one2many can't be used as condition for defaults
511 assert(self.change_default != True)
513 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
517 context = context.copy()
518 context.update(self._context)
526 domain = self._domain(obj) if callable(self._domain) else self._domain
527 ids2 = obj.pool.get(self._obj).search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
528 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
529 if r[self._fields_id] in res:
530 res[r[self._fields_id]].append(r['id'])
533 def set(self, cr, obj, id, field, values, user=None, context=None):
538 context = context.copy()
539 context.update(self._context)
540 context['no_store_function'] = True
543 _table = obj.pool.get(self._obj)._table
544 obj = obj.pool.get(self._obj)
547 act[2][self._fields_id] = id
548 id_new = obj.create(cr, user, act[2], context=context)
549 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
551 obj.write(cr, user, [act[1]], act[2], context=context)
553 obj.unlink(cr, user, [act[1]], context=context)
555 reverse_rel = obj._all_columns.get(self._fields_id)
556 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
557 # if the model has on delete cascade, just delete the row
558 if reverse_rel.column.ondelete == "cascade":
559 obj.unlink(cr, user, [act[1]], context=context)
561 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
563 # Must use write() to recompute parent_store structure if needed
564 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
566 reverse_rel = obj._all_columns.get(self._fields_id)
567 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
568 # if the o2m has a static domain we must respect it when unlinking
569 domain = self._domain(obj) if callable(self._domain) else self._domain
570 extra_domain = domain or []
571 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
572 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
573 # otherwise we only nullify the reverse foreign key column.
574 if reverse_rel.column.ondelete == "cascade":
575 obj.unlink(cr, user, ids_to_unlink, context=context)
577 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
579 # Must use write() to recompute parent_store structure if needed
580 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
582 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
583 ids3 = map(lambda x:x[0], cr.fetchall())
584 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
587 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
588 domain = self._domain(obj) if callable(self._domain) else self._domain
589 return obj.pool.get(self._obj).name_search(cr, uid, value, domain, operator, context=context,limit=limit)
593 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
594 raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)')
597 # Values: (0, 0, { fields }) create
598 # (1, ID, { fields }) update (write fields to ID)
599 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
600 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
601 # (4, ID) link (add a relationship)
603 # (6, ?, ids) set a list of links
605 class many2many(_column):
606 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
607 low-level details of the intermediary relationship table transparently.
608 A many-to-many relationship is always symmetrical, and can be declared and accessed
609 from either endpoint model.
610 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
611 or id2 (destination foreign key column name) are not specified, the system will
612 provide default values. This will by default only allow one single symmetrical
613 many-to-many relationship between the source and destination model.
614 For multiple many-to-many relationship between the same models and for
615 relationships where source and destination models are the same, ``rel``, ``id1``
616 and ``id2`` should be specified explicitly.
618 :param str obj: destination model
619 :param str rel: optional name of the intermediary relationship table. If not specified,
620 a canonical name will be derived based on the alphabetically-ordered
621 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
622 Automatic naming is not possible when the source and destination are
623 the same, for obvious ambiguity reasons.
624 :param str id1: optional name for the column holding the foreign key to the current
625 model in the relationship table. If not specified, a canonical name
626 will be derived based on the model name (in the form: `src_model_id`).
627 :param str id2: optional name for the column holding the foreign key to the destination
628 model in the relationship table. If not specified, a canonical name
629 will be derived based on the model name (in the form: `dest_model_id`)
630 :param str string: field label
632 _classic_read = False
633 _classic_write = False
637 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
640 _column.__init__(self, string=string, **args)
642 if rel and '.' in rel:
643 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
644 'You used %s, which is not a valid SQL table name.')% (string,rel))
650 def _sql_names(self, source_model):
651 """Return the SQL names defining the structure of the m2m relationship table
653 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
654 local_col is the name of the column holding the current model's FK, and
655 dest_col is the name of the column holding the destination model's FK, and
657 tbl, col1, col2 = self._rel, self._id1, self._id2
658 if not all((tbl, col1, col2)):
659 # the default table name is based on the stable alphabetical order of tables
660 dest_model = source_model.pool.get(self._obj)
661 tables = tuple(sorted([source_model._table, dest_model._table]))
663 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
664 'is not possible when source and destination models are '\
666 tbl = '%s_%s_rel' % tables
668 col1 = '%s_id' % source_model._table
670 col2 = '%s_id' % dest_model._table
671 return (tbl, col1, col2)
673 def _get_query_and_where_params(self, cr, model, ids, values, where_params):
674 """ Extracted from ``get`` to facilitate fine-tuning of the generated
676 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
677 FROM %(rel)s, %(from_c)s \
678 WHERE %(rel)s.%(id1)s IN %%s \
679 AND %(rel)s.%(id2)s = %(tbl)s.id \
685 return query, where_params
687 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
699 "Specifying offset at a many2many.get() is deprecated and may"
700 " produce unpredictable results.")
701 obj = model.pool.get(self._obj)
702 rel, id1, id2 = self._sql_names(model)
704 # static domains are lists, and are evaluated both here and on client-side, while string
705 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
706 # FIXME: make this distinction explicit in API!
707 domain = isinstance(self._domain, list) and self._domain or []
709 wquery = obj._where_calc(cr, user, domain, context=context)
710 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
711 from_c, where_c, where_params = wquery.get_sql()
713 where_c = ' AND ' + where_c
715 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
718 if self._limit is not None:
719 limit_str = ' LIMIT %d' % self._limit
721 query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel,
728 'order_by': order_by,
732 cr.execute(query, [tuple(ids),] + where_params)
733 for r in cr.fetchall():
734 res[r[1]].append(r[0])
737 def set(self, cr, model, id, name, values, user=None, context=None):
742 rel, id1, id2 = self._sql_names(model)
743 obj = model.pool.get(self._obj)
745 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
748 idnew = obj.create(cr, user, act[2], context=context)
749 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
751 obj.write(cr, user, [act[1]], act[2], context=context)
753 obj.unlink(cr, user, [act[1]], context=context)
755 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
757 # following queries are in the same transaction - so should be relatively safe
758 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
759 if not cr.fetchone():
760 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
762 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
765 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
767 d1 = ' and ' + ' and '.join(d1)
770 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)
772 for act_nbr in act[2]:
773 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
776 # TODO: use a name_search
778 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
779 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
782 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
783 raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)')
786 def get_nice_size(value):
788 if isinstance(value, (int,long)):
790 elif value: # this is supposed to be a string
792 return tools.human_size(size)
794 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
795 # and http://bugs.python.org/issue10066
796 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
798 def sanitize_binary_value(value):
799 # binary fields should be 7-bit ASCII base64-encoded data,
800 # but we do additional sanity checks to make sure the values
801 # are not something else that won't pass via XML-RPC
802 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
803 # these builtin types are meant to pass untouched
806 # Handle invalid bytes values that will cause problems
807 # for XML-RPC. See for more info:
808 # - http://bugs.python.org/issue10066
809 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
811 # Coercing to unicode would normally allow it to properly pass via
812 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
813 # (this works for _any_ byte values, thanks to the fallback
814 # to latin-1 passthrough encoding when decoding to unicode)
815 value = tools.ustr(value)
817 # Due to Python bug #10066 this could still yield invalid XML
818 # bytes, specifically in the low byte range, that will crash
819 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
820 # So check for low bytes values, and if any, perform
821 # base64 encoding - not very smart or useful, but this is
822 # our last resort to avoid crashing the request.
823 if invalid_xml_low_bytes.search(value):
824 # b64-encode after restoring the pure bytes with latin-1
825 # passthrough encoding
826 value = base64.b64encode(value.encode('latin-1'))
831 # ---------------------------------------------------------
833 # ---------------------------------------------------------
834 class function(_column):
836 A field whose value is computed by a function (rather
837 than being read from the database).
839 :param fnct: the callable that will compute the field value.
840 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
841 :param fnct_inv: the callable that will allow writing values in that field
842 (if not provided, the field is read-only).
843 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
845 :param str type: type of the field simulated by the function field
846 :param fnct_search: the callable that allows searching on the field
847 (if not provided, search will not return any result).
848 :param store: store computed value in database
849 (see :ref:`The *store* parameter <field-function-store>`).
850 :type store: True or dict specifying triggers for field computation
851 :param multi: name of batch for batch computation of function fields.
852 All fields with the same batch name will be computed by
853 a single function call. This changes the signature of the
856 .. _field-function-fnct: The ``fnct`` parameter
858 .. rubric:: The ``fnct`` parameter
860 The callable implementing the function field must have the following signature:
862 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
864 Implements the function field.
866 :param orm model: model to which the field belongs (should be ``self`` for
868 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
869 list of field names to compute.
870 :type field_name(s): str | [str]
871 :param arg: arbitrary value passed when declaring the function field
873 :return: mapping of ``ids`` to computed values, or if multi is provided,
874 to a map of field_names to computed values
876 The values in the returned dictionary must be of the type specified by the type
877 argument in the field declaration.
879 Here is an example with a simple function ``char`` function field::
882 def compute(self, cr, uid, ids, field_name, arg, context):
886 _columns['my_char'] = fields.function(compute, type='char', size=50)
888 # when called with ``ids=[1,2,3]``, ``compute`` could return:
892 3: False # null values should be returned explicitly too
895 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
896 of the field names that should be computed. Each value in the returned
897 dictionary must then be a dictionary mapping field names to values.
899 Here is an example where two function fields (``name`` and ``age``)
900 are both computed by a single function field::
903 def compute(self, cr, uid, ids, field_names, arg, context):
907 _columns['name'] = fields.function(compute_person_data, type='char',\
908 size=50, multi='person_data')
909 _columns[''age'] = fields.function(compute_person_data, type='integer',\
912 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
914 1: {'name': 'Bob', 'age': 23},
915 2: {'name': 'Sally', 'age': 19},
916 3: {'name': 'unknown', 'age': False}
919 .. _field-function-fnct-inv:
921 .. rubric:: The ``fnct_inv`` parameter
923 This callable implements the write operation for the function field
924 and must have the following signature:
926 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
928 Callable that implements the ``write`` operation for the function field.
930 :param orm model: model to which the field belongs (should be ``self`` for
932 :param int id: the identifier of the object to write on
933 :param str field_name: name of the field to set
934 :param fnct_inv_arg: arbitrary value passed when declaring the function field
937 When writing values for a function field, the ``multi`` parameter is ignored.
939 .. _field-function-fnct-search:
941 .. rubric:: The ``fnct_search`` parameter
943 This callable implements the search operation for the function field
944 and must have the following signature:
946 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
948 Callable that implements the ``search`` operation for the function field by expanding
949 a search criterion based on the function field into a new domain based only on
950 columns that are stored in the database.
952 :param orm model: model to which the field belongs (should be ``self`` for
954 :param orm model_again: same value as ``model`` (seriously! this is for backwards
956 :param str field_name: name of the field to search on
957 :param list criterion: domain component specifying the search criterion on the field.
959 :return: domain to use instead of ``criterion`` when performing the search.
960 This new domain must be based only on columns stored in the database, as it
961 will be used directly without any translation.
963 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
964 The most generic way to implement ``fnct_search`` is to directly search for the records that
965 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
966 ``[('id','in',[1,3,5])]``.
968 .. _field-function-store:
970 .. rubric:: The ``store`` parameter
972 The ``store`` parameter allows caching the result of the field computation in the
973 database, and defining the triggers that will invalidate that cache and force a
974 recomputation of the function field.
975 When not provided, the field is computed every time its value is read.
976 The value of ``store`` may be either ``True`` (to recompute the field value whenever
977 any field in the same record is modified), or a dictionary specifying a more
978 flexible set of recomputation triggers.
980 A trigger specification is a dictionary that maps the names of the models that
981 will trigger the computation, to a tuple describing the trigger rule, in the
985 'trigger_model': (mapping_function,
986 ['trigger_field1', 'trigger_field2'],
990 A trigger rule is defined by a 3-item tuple where:
992 * The ``mapping_function`` is defined as follows:
994 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
996 Callable that maps record ids of a trigger model to ids of the
997 corresponding records in the source model (whose field values
998 need to be recomputed).
1000 :param orm model: trigger_model
1001 :param list trigger_ids: ids of the records of trigger_model that were
1004 :return: list of ids of the source model whose function field values
1005 need to be recomputed
1007 * The second item is a list of the fields who should act as triggers for
1008 the computation. If an empty list is given, all fields will act as triggers.
1009 * The last item is the priority, used to order the triggers when processing them
1010 after any write operation on a model that has function field triggers. The
1011 default priority is 10.
1013 In fact, setting store = True is the same as using the following trigger dict::
1016 'model_itself': (lambda self, cr, uid, ids, context: ids,
1022 _classic_read = False
1023 _classic_write = False
1029 # multi: compute several fields in one call
1031 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):
1032 _column.__init__(self, **args)
1035 self._fnct_inv = fnct_inv
1038 if 'relation' in args:
1039 self._obj = args['relation']
1041 self.digits = args.get('digits', (16,2))
1042 self.digits_compute = args.get('digits_compute', None)
1044 self._fnct_inv_arg = fnct_inv_arg
1048 self._fnct_search = fnct_search
1051 if not fnct_search and not store:
1052 self.selectable = False
1055 if self._type != 'many2one':
1056 # m2o fields need to return tuples with name_get, not just foreign keys
1057 self._classic_read = True
1058 self._classic_write = True
1060 self._symbol_get=lambda x:x and str(x)
1063 self._symbol_c = float._symbol_c
1064 self._symbol_f = float._symbol_f
1065 self._symbol_set = float._symbol_set
1067 if type == 'boolean':
1068 self._symbol_c = boolean._symbol_c
1069 self._symbol_f = boolean._symbol_f
1070 self._symbol_set = boolean._symbol_set
1072 if type == 'integer':
1073 self._symbol_c = integer._symbol_c
1074 self._symbol_f = integer._symbol_f
1075 self._symbol_set = integer._symbol_set
1077 def digits_change(self, cr):
1078 if self._type == 'float':
1079 if self.digits_compute:
1080 self.digits = self.digits_compute(cr)
1082 precision, scale = self.digits
1083 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1084 precision_digits=scale),
1085 precision_digits=scale))
1087 def search(self, cr, uid, obj, name, args, context=None):
1088 if not self._fnct_search:
1089 #CHECKME: should raise an exception
1091 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1093 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1097 field_type = obj._columns[field]._type
1098 if field_type == "many2one":
1099 # make the result a tuple if it is not already one
1100 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1101 obj_model = obj.pool.get(obj._columns[field].relation)
1102 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1103 result = (value, dict_names[value])
1105 if field_type == 'binary':
1106 if context.get('bin_size'):
1107 # client requests only the size of binary fields
1108 result = get_nice_size(value)
1109 elif not context.get('bin_raw'):
1110 result = sanitize_binary_value(value)
1112 if field_type == "integer" and value > xmlrpclib.MAXINT:
1113 # integer/long values greater than 2^31-1 are not supported
1114 # in pure XMLRPC, so we have to pass them as floats :-(
1115 # This is not needed for stored fields and non-functional integer
1116 # fields, as their values are constrained by the database backend
1117 # to the same 32bits signed int limit.
1118 result = __builtin__.float(value)
1121 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1122 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1124 if self._multi and id in result:
1125 for field, value in result[id].iteritems():
1127 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1128 elif result.get(id):
1129 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1132 def set(self, cr, obj, id, name, value, user=None, context=None):
1136 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1139 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
1140 # Function fields are supposed to emulate a basic field type,
1141 # so they can delegate to the basic type for record name rendering
1142 return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
1144 # ---------------------------------------------------------
1146 # ---------------------------------------------------------
1148 class related(function):
1149 """Field that points to some data inside another field of the current record.
1154 'foo_id': fields.many2one('my.foo', 'Foo'),
1155 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1159 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1160 # assume self._arg = ('foo', 'bar', 'baz')
1161 # domain = [(name, op, val)] => search [('foo.bar.baz', op, val)]
1162 field = '.'.join(self._arg)
1163 return map(lambda x: (field, x[1], x[2]), domain)
1165 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1166 if isinstance(ids, (int, long)):
1168 for record in obj.browse(cr, uid, ids, context=context):
1169 # traverse all fields except the last one
1170 for field in self.arg[:-1]:
1171 record = record[field] or False
1174 elif isinstance(record, list):
1175 # record is the result of a one2many or many2many field
1178 # write on the last field
1179 record.write({self.arg[-1]: values})
1181 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1183 for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
1185 for field in self.arg:
1186 if isinstance(value, list):
1188 value = value[field] or False
1191 res[record.id] = value
1193 if self._type == 'many2one':
1194 # res[id] is a browse_record or False; convert it to (id, name) or False.
1195 # Perform name_get as root, as seeing the name of a related object depends on
1196 # access right of source document, not target, so user may not have access.
1197 value_ids = list(set(value.id for value in res.itervalues() if value))
1198 value_name = dict(obj.pool.get(self._obj).name_get(cr, SUPERUSER_ID, value_ids, context=context))
1199 res = dict((id, value and (value.id, value_name[value.id])) for id, value in res.iteritems())
1201 elif self._type in ('one2many', 'many2many'):
1202 # res[id] is a list of browse_record or False; convert it to a list of ids
1203 res = dict((id, value and map(int, value) or []) for id, value in res.iteritems())
1207 def __init__(self, *arg, **args):
1209 self._relations = []
1210 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1211 if self.store is True:
1212 # TODO: improve here to change self.store = {...} according to related objects
1216 class sparse(function):
1218 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1220 + For a many2many field, a list of tuples is expected.
1221 Here is the list of tuple that are accepted, with the corresponding semantics ::
1223 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1224 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1225 (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)
1226 (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)
1227 (4, ID) link to existing record with id = ID (adds a relationship)
1228 (5) unlink all (like using (3,ID) for all linked records)
1229 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1232 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1234 + For a one2many field, a lits of tuples is expected.
1235 Here is the list of tuple that are accepted, with the corresponding semantics ::
1237 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1238 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1239 (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)
1242 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1245 if self._type == 'many2many':
1246 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1249 elif self._type == 'one2many':
1252 relation_obj = obj.pool.get(self.relation)
1254 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1256 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1258 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1260 relation_obj.unlink(cr, uid, vals[1], context=context)
1261 read_value.remove(vals[1])
1266 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1267 if not type(ids) == list:
1269 records = obj.browse(cr, uid, ids, context=context)
1270 for record in records:
1271 # grab serialized value as object - already deserialized
1272 serialized = getattr(record, self.serialization_field)
1274 # simply delete the key to unset it.
1275 serialized.pop(field_name, None)
1277 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1278 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1281 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1283 records = obj.browse(cr, uid, ids, context=context)
1284 for record in records:
1285 # grab serialized value as object - already deserialized
1286 serialized = getattr(record, self.serialization_field)
1287 results[record.id] = {}
1288 for field_name in field_names:
1289 field_type = obj._columns[field_name]._type
1290 value = serialized.get(field_name, False)
1291 if field_type in ('one2many','many2many'):
1294 # filter out deleted records as superuser
1295 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1296 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1297 if type(value) in (int,long) and field_type == 'many2one':
1298 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1299 # check for deleted record as superuser
1300 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1302 results[record.id][field_name] = value
1305 def __init__(self, serialization_field, **kwargs):
1306 self.serialization_field = serialization_field
1307 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1311 # ---------------------------------------------------------
1313 # ---------------------------------------------------------
1315 class dummy(function):
1316 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1319 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1322 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1325 def __init__(self, *arg, **args):
1327 self._relations = []
1328 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1330 # ---------------------------------------------------------
1332 # ---------------------------------------------------------
1334 class serialized(_column):
1335 """ A field able to store an arbitrary python data structure.
1337 Note: only plain components allowed.
1340 def _symbol_set_struct(val):
1341 return simplejson.dumps(val)
1343 def _symbol_get_struct(self, val):
1344 return simplejson.loads(val or '{}')
1347 _type = 'serialized'
1350 _symbol_f = _symbol_set_struct
1351 _symbol_set = (_symbol_c, _symbol_f)
1352 _symbol_get = _symbol_get_struct
1354 # TODO: review completly this class for speed improvement
1355 class property(function):
1357 def _get_default(self, obj, cr, uid, prop_name, context=None):
1358 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1360 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1361 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1363 :param list of string prop_names: list of name of property fields for those we want the default value
1364 :return: map of property field names to their default value
1367 prop = obj.pool.get('ir.property')
1369 for prop_name in prop_names:
1370 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1373 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1374 prop = obj.pool.get('ir.property')
1375 vids = [obj._name + ',' + str(oid) for oid in ids]
1377 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1378 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1380 domain = [('res_id', 'in', vids)] + domain
1381 return prop.search(cr, uid, domain, context=context)
1383 # TODO: to rewrite more clean
1384 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1388 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1390 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1392 default_val = self._get_default(obj, cr, uid, prop_name, context)
1394 property_create = False
1395 if isinstance(default_val, openerp.osv.orm.browse_record):
1396 if default_val.id != id_val:
1397 property_create = True
1398 elif default_val != id_val:
1399 property_create = True
1402 def_id = self._field_get(cr, uid, obj._name, prop_name)
1403 company = obj.pool.get('res.company')
1404 cid = company._company_default_get(cr, uid, obj._name, def_id,
1406 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1408 prop = obj.pool.get('ir.property')
1409 return prop.create(cr, uid, {
1410 'name': propdef.name,
1412 'res_id': obj._name+','+str(id),
1414 'fields_id': def_id,
1419 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1420 prop = obj.pool.get('ir.property')
1421 # get the default values (for res_id = False) for the property fields
1422 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1424 # build the dictionary that will be returned
1427 res[id] = default_val.copy()
1429 for prop_name in prop_names:
1430 property_field = obj._all_columns.get(prop_name).column
1431 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1432 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1433 # in order to make a name_get in batch for all the ids needed.
1436 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1437 obj_reference = obj._name + ',' + str(id)
1438 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1440 res[id][prop_name] = value
1441 # Check existence as root (as seeing the name of a related
1442 # object depends on access right of source document,
1443 # not target, so user may not have access) in order to avoid
1444 # pointing on an unexisting record.
1445 if property_destination_obj:
1446 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, SUPERUSER_ID, res[id][prop_name].id):
1447 name_get_ids[id] = res[id][prop_name].id
1449 res[id][prop_name] = False
1450 if property_destination_obj:
1451 # name_get as root (as seeing the name of a related
1452 # object depends on access right of source document,
1453 # not target, so user may not have access.)
1454 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, SUPERUSER_ID, name_get_ids.values(), context=context))
1455 # the property field is a m2o, we need to return a tuple with (id, name)
1456 for k, v in name_get_ids.iteritems():
1457 if res[k][prop_name]:
1458 res[k][prop_name] = (v , name_get_values.get(v))
1461 def _field_get(self, cr, uid, model_name, prop):
1462 if not self.field_id.get(cr.dbname):
1463 cr.execute('SELECT id \
1464 FROM ir_model_fields \
1465 WHERE name=%s AND model=%s', (prop, model_name))
1467 self.field_id[cr.dbname] = res and res[0]
1468 return self.field_id[cr.dbname]
1470 def __init__(self, obj_prop, **args):
1471 # TODO remove obj_prop parameter (use many2one type)
1473 function.__init__(self, self._fnct_read, False, self._fnct_write,
1474 obj_prop, multi='properties', **args)
1480 def field_to_dict(model, cr, user, field, context=None):
1481 """ Return a dictionary representation of a field.
1483 The string, help, and selection attributes (if any) are untranslated. This
1484 representation is the one returned by fields_get() (fields_get() will do
1489 res = {'type': field._type}
1490 # some attributes for m2m/function field are added as debug info only
1491 if isinstance(field, function):
1492 res['function'] = field._fnct and field._fnct.func_name or False
1493 res['store'] = field.store
1494 if isinstance(field.store, dict):
1495 res['store'] = str(field.store)
1496 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1497 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1498 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1499 if isinstance(field, many2many):
1500 (table, col1, col2) = field._sql_names(model)
1501 res['m2m_join_columns'] = [col1, col2]
1502 res['m2m_join_table'] = table
1503 for arg in ('string', 'readonly', 'states', 'size', 'group_operator', 'required',
1504 'change_default', 'translate', 'help', 'select', 'selectable', 'groups',
1505 'deprecated', 'digits', 'invisible', 'filters'):
1506 if getattr(field, arg, None):
1507 res[arg] = getattr(field, arg)
1509 if hasattr(field, 'selection'):
1510 if isinstance(field.selection, (tuple, list)):
1511 res['selection'] = field.selection
1513 # call the 'dynamic selection' function
1514 res['selection'] = field.selection(model, cr, user, context)
1515 if res['type'] in ('one2many', 'many2many', 'many2one'):
1516 res['relation'] = field._obj
1517 res['domain'] = field._domain(model) if callable(field._domain) else field._domain
1518 res['context'] = field._context
1520 if isinstance(field, one2many):
1521 res['relation_field'] = field._fields_id
1526 class column_info(object):
1527 """ Struct containing details about an osv column, either one local to
1528 its model, or one inherited via _inherits.
1534 .. attribute:: column
1536 column instance, subclass of :class:`_column`
1538 .. attribute:: parent_model
1540 if the column is inherited, name of the model that contains it,
1541 ``None`` for local columns.
1543 .. attribute:: parent_column
1545 the name of the column containing the m2o relationship to the
1546 parent model that contains this column, ``None`` for local columns.
1548 .. attribute:: original_parent
1550 if the column is inherited, name of the original parent model that
1551 contains it i.e in case of multilevel inheritance, ``None`` for
1554 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1556 self.column = column
1557 self.parent_model = parent_model
1558 self.parent_column = parent_column
1559 self.original_parent = original_parent
1562 return '%s(%s, %s, %s, %s, %s)' % (
1563 self.__name__, self.name, self.column,
1564 self.parent_model, self.parent_column, self.original_parent)
1566 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: