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[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 if model_name in obj.pool and res_id:
207 model = obj.pool[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 - automatically passed when used in
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'):
306 tz_name = context['tz']
308 tz_name = model.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
311 utc = pytz.timezone('UTC')
312 context_tz = pytz.timezone(tz_name)
313 utc_today = utc.localize(today, is_dst=False) # UTC = no DST
314 context_today = utc_today.astimezone(context_tz)
316 _logger.debug("failed to compute context/client-specific today date, "
317 "using the UTC value for `today`",
319 return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
321 class datetime(_column):
325 """ Returns the current datetime in a format fit for being a
326 default value to a ``datetime`` field.
328 This method should be provided as is to the _defaults dict, it
329 should not be called.
331 return DT.datetime.now().strftime(
332 tools.DEFAULT_SERVER_DATETIME_FORMAT)
335 def context_timestamp(cr, uid, timestamp, context=None):
336 """Returns the given timestamp converted to the client's timezone.
337 This method is *not* meant for use as a _defaults initializer,
338 because datetime fields are automatically converted upon
339 display on client side. For _defaults you :meth:`fields.datetime.now`
340 should be used instead.
342 :param datetime timestamp: naive datetime value (expressed in UTC)
343 to be converted to the client timezone
344 :param dict context: the 'tz' key in the context should give the
345 name of the User/Client timezone (otherwise
348 :return: timestamp converted to timezone-aware datetime in context
351 assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
352 if context and context.get('tz'):
353 tz_name = context['tz']
355 registry = openerp.modules.registry.RegistryManager.get(cr.dbname)
356 tz_name = registry.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
359 utc = pytz.timezone('UTC')
360 context_tz = pytz.timezone(tz_name)
361 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
362 return utc_timestamp.astimezone(context_tz)
364 _logger.debug("failed to compute context/client-specific timestamp, "
365 "using the UTC value",
369 class binary(_column):
373 # Binary values may be byte strings (python 2.6 byte array), but
374 # the legacy OpenERP convention is to transfer and store binaries
375 # as base64-encoded strings. The base64 string may be provided as a
376 # unicode in some circumstances, hence the str() cast in symbol_f.
377 # This str coercion will only work for pure ASCII unicode strings,
378 # on purpose - non base64 data must be passed as a 8bit byte strings.
379 _symbol_f = lambda symb: symb and Binary(str(symb)) or None
381 _symbol_set = (_symbol_c, _symbol_f)
382 _symbol_get = lambda self, x: x and str(x)
384 _classic_read = False
387 def __init__(self, string='unknown', filters=None, **args):
388 _column.__init__(self, string=string, **args)
389 self.filters = filters
391 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
404 # If client is requesting only the size of the field, we return it instead
405 # of the content. Presumably a separate request will be done to read the actual
406 # content if it's needed at some point.
407 # TODO: after 6.0 we should consider returning a dict with size and content instead of
408 # having an implicit convention for the value
409 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
410 res[i] = tools.human_size(long(val))
415 class selection(_column):
418 def __init__(self, selection, string='unknown', **args):
419 _column.__init__(self, string=string, **args)
420 self.selection = selection
422 # ---------------------------------------------------------
424 # ---------------------------------------------------------
427 # Values: (0, 0, { fields }) create
428 # (1, ID, { fields }) update
429 # (2, ID) remove (delete)
430 # (3, ID) unlink one (target id or target of relation)
432 # (5) unlink all (only valid for one2many)
435 class many2one(_column):
436 _classic_read = False
437 _classic_write = True
440 _symbol_f = lambda x: x or None
441 _symbol_set = (_symbol_c, _symbol_f)
443 def __init__(self, obj, string='unknown', auto_join=False, **args):
444 _column.__init__(self, string=string, **args)
446 self._auto_join = auto_join
448 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
456 res[r['id']] = r[name]
458 res.setdefault(id, '')
459 obj = obj.pool[self._obj]
461 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
462 # we use uid=1 because the visibility of a many2one field value (just id and name)
463 # must be the access right of the parent form and not the linked object itself.
464 records = dict(obj.name_get(cr, SUPERUSER_ID,
465 list(set([x for x in res.values() if isinstance(x, (int,long))])),
468 if res[id] in records:
469 res[id] = (res[id], records[res[id]])
474 def set(self, cr, obj_src, id, field, values, user=None, context=None):
477 obj = obj_src.pool[self._obj]
478 self._table = obj._table
479 if type(values) == type([]):
482 id_new = obj.create(cr, act[2])
483 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
485 obj.write(cr, [act[1]], act[2], context=context)
487 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
488 elif act[0] == 3 or act[0] == 5:
489 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
491 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
494 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
496 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
498 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
499 return obj.pool[self._obj].search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
503 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
504 return value[1] if isinstance(value, tuple) else tools.ustr(value)
507 class one2many(_column):
508 _classic_read = False
509 _classic_write = False
513 def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args):
514 _column.__init__(self, string=string, **args)
516 self._fields_id = fields_id
518 self._auto_join = auto_join
519 #one2many can't be used as condition for defaults
520 assert(self.change_default != True)
522 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
526 context = context.copy()
527 context.update(self._context)
535 domain = self._domain(obj) if callable(self._domain) else self._domain
536 model = obj.pool[self._obj]
537 ids2 = model.search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
538 for r in model._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
539 if r[self._fields_id] in res:
540 res[r[self._fields_id]].append(r['id'])
543 def set(self, cr, obj, id, field, values, user=None, context=None):
548 context = context.copy()
549 context.update(self._context)
550 context['no_store_function'] = True
553 obj = obj.pool[self._obj]
557 act[2][self._fields_id] = id
558 id_new = obj.create(cr, user, act[2], context=context)
559 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
561 obj.write(cr, user, [act[1]], act[2], context=context)
563 obj.unlink(cr, user, [act[1]], context=context)
565 reverse_rel = obj._all_columns.get(self._fields_id)
566 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
567 # if the model has on delete cascade, just delete the row
568 if reverse_rel.column.ondelete == "cascade":
569 obj.unlink(cr, user, [act[1]], context=context)
571 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
573 # Must use write() to recompute parent_store structure if needed
574 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
576 reverse_rel = obj._all_columns.get(self._fields_id)
577 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
578 # if the o2m has a static domain we must respect it when unlinking
579 domain = self._domain(obj) if callable(self._domain) else self._domain
580 extra_domain = domain or []
581 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
582 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
583 # otherwise we only nullify the reverse foreign key column.
584 if reverse_rel.column.ondelete == "cascade":
585 obj.unlink(cr, user, ids_to_unlink, context=context)
587 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
589 # Must use write() to recompute parent_store structure if needed
590 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
592 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
593 ids3 = map(lambda x:x[0], cr.fetchall())
594 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
597 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
598 domain = self._domain(obj) if callable(self._domain) else self._domain
599 return obj.pool[self._obj].name_search(cr, uid, value, domain, operator, context=context,limit=limit)
603 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
604 raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)')
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[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_query_and_where_params(self, cr, model, ids, values, where_params):
684 """ Extracted from ``get`` to facilitate fine-tuning of the generated
686 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
687 FROM %(rel)s, %(from_c)s \
688 WHERE %(rel)s.%(id1)s IN %%s \
689 AND %(rel)s.%(id2)s = %(tbl)s.id \
695 return query, where_params
697 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
709 "Specifying offset at a many2many.get() is deprecated and may"
710 " produce unpredictable results.")
711 obj = model.pool[self._obj]
712 rel, id1, id2 = self._sql_names(model)
714 # static domains are lists, and are evaluated both here and on client-side, while string
715 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
716 # FIXME: make this distinction explicit in API!
717 domain = isinstance(self._domain, list) and self._domain or []
719 wquery = obj._where_calc(cr, user, domain, context=context)
720 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
721 from_c, where_c, where_params = wquery.get_sql()
723 where_c = ' AND ' + where_c
725 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
728 if self._limit is not None:
729 limit_str = ' LIMIT %d' % self._limit
731 query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel,
738 'order_by': order_by,
742 cr.execute(query, [tuple(ids),] + where_params)
743 for r in cr.fetchall():
744 res[r[1]].append(r[0])
747 def set(self, cr, model, id, name, values, user=None, context=None):
752 rel, id1, id2 = self._sql_names(model)
753 obj = model.pool[self._obj]
755 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
758 idnew = obj.create(cr, user, act[2], context=context)
759 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
761 obj.write(cr, user, [act[1]], act[2], context=context)
763 obj.unlink(cr, user, [act[1]], context=context)
765 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
767 # following queries are in the same transaction - so should be relatively safe
768 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
769 if not cr.fetchone():
770 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
772 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
775 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
777 d1 = ' and ' + ' and '.join(d1)
780 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)
782 for act_nbr in act[2]:
783 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
786 # TODO: use a name_search
788 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
789 return obj.pool[self._obj].search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
792 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
793 raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)')
796 def get_nice_size(value):
798 if isinstance(value, (int,long)):
800 elif value: # this is supposed to be a string
802 return tools.human_size(size)
804 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
805 # and http://bugs.python.org/issue10066
806 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
808 def sanitize_binary_value(value):
809 # binary fields should be 7-bit ASCII base64-encoded data,
810 # but we do additional sanity checks to make sure the values
811 # are not something else that won't pass via XML-RPC
812 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
813 # these builtin types are meant to pass untouched
816 # Handle invalid bytes values that will cause problems
817 # for XML-RPC. See for more info:
818 # - http://bugs.python.org/issue10066
819 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
821 # Coercing to unicode would normally allow it to properly pass via
822 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
823 # (this works for _any_ byte values, thanks to the fallback
824 # to latin-1 passthrough encoding when decoding to unicode)
825 value = tools.ustr(value)
827 # Due to Python bug #10066 this could still yield invalid XML
828 # bytes, specifically in the low byte range, that will crash
829 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
830 # So check for low bytes values, and if any, perform
831 # base64 encoding - not very smart or useful, but this is
832 # our last resort to avoid crashing the request.
833 if invalid_xml_low_bytes.search(value):
834 # b64-encode after restoring the pure bytes with latin-1
835 # passthrough encoding
836 value = base64.b64encode(value.encode('latin-1'))
841 # ---------------------------------------------------------
843 # ---------------------------------------------------------
844 class function(_column):
846 A field whose value is computed by a function (rather
847 than being read from the database).
849 :param fnct: the callable that will compute the field value.
850 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
851 :param fnct_inv: the callable that will allow writing values in that field
852 (if not provided, the field is read-only).
853 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
855 :param str type: type of the field simulated by the function field
856 :param fnct_search: the callable that allows searching on the field
857 (if not provided, search will not return any result).
858 :param store: store computed value in database
859 (see :ref:`The *store* parameter <field-function-store>`).
860 :type store: True or dict specifying triggers for field computation
861 :param multi: name of batch for batch computation of function fields.
862 All fields with the same batch name will be computed by
863 a single function call. This changes the signature of the
866 .. _field-function-fnct: The ``fnct`` parameter
868 .. rubric:: The ``fnct`` parameter
870 The callable implementing the function field must have the following signature:
872 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
874 Implements the function field.
876 :param orm model: model to which the field belongs (should be ``self`` for
878 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
879 list of field names to compute.
880 :type field_name(s): str | [str]
881 :param arg: arbitrary value passed when declaring the function field
883 :return: mapping of ``ids`` to computed values, or if multi is provided,
884 to a map of field_names to computed values
886 The values in the returned dictionary must be of the type specified by the type
887 argument in the field declaration.
889 Here is an example with a simple function ``char`` function field::
892 def compute(self, cr, uid, ids, field_name, arg, context):
896 _columns['my_char'] = fields.function(compute, type='char', size=50)
898 # when called with ``ids=[1,2,3]``, ``compute`` could return:
902 3: False # null values should be returned explicitly too
905 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
906 of the field names that should be computed. Each value in the returned
907 dictionary must then be a dictionary mapping field names to values.
909 Here is an example where two function fields (``name`` and ``age``)
910 are both computed by a single function field::
913 def compute(self, cr, uid, ids, field_names, arg, context):
917 _columns['name'] = fields.function(compute_person_data, type='char',\
918 size=50, multi='person_data')
919 _columns[''age'] = fields.function(compute_person_data, type='integer',\
922 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
924 1: {'name': 'Bob', 'age': 23},
925 2: {'name': 'Sally', 'age': 19},
926 3: {'name': 'unknown', 'age': False}
929 .. _field-function-fnct-inv:
931 .. rubric:: The ``fnct_inv`` parameter
933 This callable implements the write operation for the function field
934 and must have the following signature:
936 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
938 Callable that implements the ``write`` operation for the function field.
940 :param orm model: model to which the field belongs (should be ``self`` for
942 :param int id: the identifier of the object to write on
943 :param str field_name: name of the field to set
944 :param fnct_inv_arg: arbitrary value passed when declaring the function field
947 When writing values for a function field, the ``multi`` parameter is ignored.
949 .. _field-function-fnct-search:
951 .. rubric:: The ``fnct_search`` parameter
953 This callable implements the search operation for the function field
954 and must have the following signature:
956 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
958 Callable that implements the ``search`` operation for the function field by expanding
959 a search criterion based on the function field into a new domain based only on
960 columns that are stored in the database.
962 :param orm model: model to which the field belongs (should be ``self`` for
964 :param orm model_again: same value as ``model`` (seriously! this is for backwards
966 :param str field_name: name of the field to search on
967 :param list criterion: domain component specifying the search criterion on the field.
969 :return: domain to use instead of ``criterion`` when performing the search.
970 This new domain must be based only on columns stored in the database, as it
971 will be used directly without any translation.
973 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
974 The most generic way to implement ``fnct_search`` is to directly search for the records that
975 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
976 ``[('id','in',[1,3,5])]``.
978 .. _field-function-store:
980 .. rubric:: The ``store`` parameter
982 The ``store`` parameter allows caching the result of the field computation in the
983 database, and defining the triggers that will invalidate that cache and force a
984 recomputation of the function field.
985 When not provided, the field is computed every time its value is read.
986 The value of ``store`` may be either ``True`` (to recompute the field value whenever
987 any field in the same record is modified), or a dictionary specifying a more
988 flexible set of recomputation triggers.
990 A trigger specification is a dictionary that maps the names of the models that
991 will trigger the computation, to a tuple describing the trigger rule, in the
995 'trigger_model': (mapping_function,
996 ['trigger_field1', 'trigger_field2'],
1000 A trigger rule is defined by a 3-item tuple where:
1002 * The ``mapping_function`` is defined as follows:
1004 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
1006 Callable that maps record ids of a trigger model to ids of the
1007 corresponding records in the source model (whose field values
1008 need to be recomputed).
1010 :param orm model: trigger_model
1011 :param list trigger_ids: ids of the records of trigger_model that were
1014 :return: list of ids of the source model whose function field values
1015 need to be recomputed
1017 * The second item is a list of the fields who should act as triggers for
1018 the computation. If an empty list is given, all fields will act as triggers.
1019 * The last item is the priority, used to order the triggers when processing them
1020 after any write operation on a model that has function field triggers. The
1021 default priority is 10.
1023 In fact, setting store = True is the same as using the following trigger dict::
1026 'model_itself': (lambda self, cr, uid, ids, context: ids,
1032 _classic_read = False
1033 _classic_write = False
1039 # multi: compute several fields in one call
1041 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):
1042 _column.__init__(self, **args)
1045 self._fnct_inv = fnct_inv
1048 if 'relation' in args:
1049 self._obj = args['relation']
1051 self.digits = args.get('digits', (16,2))
1052 self.digits_compute = args.get('digits_compute', None)
1054 self._fnct_inv_arg = fnct_inv_arg
1058 self._fnct_search = fnct_search
1061 if not fnct_search and not store:
1062 self.selectable = False
1065 if self._type != 'many2one':
1066 # m2o fields need to return tuples with name_get, not just foreign keys
1067 self._classic_read = True
1068 self._classic_write = True
1070 self._symbol_get=lambda x:x and str(x)
1072 self._prefetch = True
1075 self._symbol_c = float._symbol_c
1076 self._symbol_f = float._symbol_f
1077 self._symbol_set = float._symbol_set
1079 if type == 'boolean':
1080 self._symbol_c = boolean._symbol_c
1081 self._symbol_f = boolean._symbol_f
1082 self._symbol_set = boolean._symbol_set
1084 if type == 'integer':
1085 self._symbol_c = integer._symbol_c
1086 self._symbol_f = integer._symbol_f
1087 self._symbol_set = integer._symbol_set
1089 def digits_change(self, cr):
1090 if self._type == 'float':
1091 if self.digits_compute:
1092 self.digits = self.digits_compute(cr)
1094 precision, scale = self.digits
1095 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1096 precision_digits=scale),
1097 precision_digits=scale))
1099 def search(self, cr, uid, obj, name, args, context=None):
1100 if not self._fnct_search:
1101 #CHECKME: should raise an exception
1103 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1105 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1109 field_type = obj._columns[field]._type
1110 if field_type == "many2one":
1111 # make the result a tuple if it is not already one
1112 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1113 obj_model = obj.pool[obj._columns[field].relation]
1114 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1115 result = (value, dict_names[value])
1117 if field_type == 'binary':
1118 if context.get('bin_size'):
1119 # client requests only the size of binary fields
1120 result = get_nice_size(value)
1121 elif not context.get('bin_raw'):
1122 result = sanitize_binary_value(value)
1124 if field_type == "integer" and value > xmlrpclib.MAXINT:
1125 # integer/long values greater than 2^31-1 are not supported
1126 # in pure XMLRPC, so we have to pass them as floats :-(
1127 # This is not needed for stored fields and non-functional integer
1128 # fields, as their values are constrained by the database backend
1129 # to the same 32bits signed int limit.
1130 result = __builtin__.float(value)
1133 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1134 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1136 if self._multi and id in result:
1137 for field, value in result[id].iteritems():
1139 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1140 elif result.get(id):
1141 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1144 def set(self, cr, obj, id, name, value, user=None, context=None):
1148 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1151 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
1152 # Function fields are supposed to emulate a basic field type,
1153 # so they can delegate to the basic type for record name rendering
1154 return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
1156 # ---------------------------------------------------------
1158 # ---------------------------------------------------------
1160 class related(function):
1161 """Field that points to some data inside another field of the current record.
1166 'foo_id': fields.many2one('my.foo', 'Foo'),
1167 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1171 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1172 # assume self._arg = ('foo', 'bar', 'baz')
1173 # domain = [(name, op, val)] => search [('foo.bar.baz', op, val)]
1174 field = '.'.join(self._arg)
1175 return map(lambda x: (field, x[1], x[2]), domain)
1177 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1178 if isinstance(ids, (int, long)):
1180 for record in obj.browse(cr, uid, ids, context=context):
1181 # traverse all fields except the last one
1182 for field in self.arg[:-1]:
1183 record = record[field] or False
1186 elif isinstance(record, list):
1187 # record is the result of a one2many or many2many field
1190 # write on the last field
1191 record.write({self.arg[-1]: values})
1193 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1195 for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
1197 for field in self.arg:
1198 if isinstance(value, list):
1200 value = value[field] or False
1203 res[record.id] = value
1205 if self._type == 'many2one':
1206 # res[id] is a browse_record or False; convert it to (id, name) or False.
1207 # Perform name_get as root, as seeing the name of a related object depends on
1208 # access right of source document, not target, so user may not have access.
1209 value_ids = list(set(value.id for value in res.itervalues() if value))
1210 value_name = dict(obj.pool[self._obj].name_get(cr, SUPERUSER_ID, value_ids, context=context))
1211 res = dict((id, value and (value.id, value_name[value.id])) for id, value in res.iteritems())
1213 elif self._type in ('one2many', 'many2many'):
1214 # res[id] is a list of browse_record or False; convert it to a list of ids
1215 res = dict((id, value and map(int, value) or []) for id, value in res.iteritems())
1219 def __init__(self, *arg, **args):
1221 self._relations = []
1222 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1223 if self.store is True:
1224 # TODO: improve here to change self.store = {...} according to related objects
1228 class sparse(function):
1230 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1232 + For a many2many field, a list of tuples is expected.
1233 Here is the list of tuple that are accepted, with the corresponding semantics ::
1235 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1236 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1237 (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)
1238 (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)
1239 (4, ID) link to existing record with id = ID (adds a relationship)
1240 (5) unlink all (like using (3,ID) for all linked records)
1241 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1244 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1246 + For a one2many field, a lits of tuples is expected.
1247 Here is the list of tuple that are accepted, with the corresponding semantics ::
1249 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1250 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1251 (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)
1254 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1257 if self._type == 'many2many':
1258 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1261 elif self._type == 'one2many':
1264 relation_obj = obj.pool[self.relation]
1266 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1268 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1270 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1272 relation_obj.unlink(cr, uid, vals[1], context=context)
1273 read_value.remove(vals[1])
1278 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1279 if not type(ids) == list:
1281 records = obj.browse(cr, uid, ids, context=context)
1282 for record in records:
1283 # grab serialized value as object - already deserialized
1284 serialized = getattr(record, self.serialization_field)
1286 # simply delete the key to unset it.
1287 serialized.pop(field_name, None)
1289 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1290 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1293 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1295 records = obj.browse(cr, uid, ids, context=context)
1296 for record in records:
1297 # grab serialized value as object - already deserialized
1298 serialized = getattr(record, self.serialization_field)
1299 results[record.id] = {}
1300 for field_name in field_names:
1301 field_type = obj._columns[field_name]._type
1302 value = serialized.get(field_name, False)
1303 if field_type in ('one2many','many2many'):
1306 # filter out deleted records as superuser
1307 relation_obj = obj.pool[obj._columns[field_name].relation]
1308 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1309 if type(value) in (int,long) and field_type == 'many2one':
1310 relation_obj = obj.pool[obj._columns[field_name].relation]
1311 # check for deleted record as superuser
1312 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1314 results[record.id][field_name] = value
1317 def __init__(self, serialization_field, **kwargs):
1318 self.serialization_field = serialization_field
1319 super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1323 # ---------------------------------------------------------
1325 # ---------------------------------------------------------
1327 class dummy(function):
1328 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1331 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1334 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1337 def __init__(self, *arg, **args):
1339 self._relations = []
1340 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1342 # ---------------------------------------------------------
1344 # ---------------------------------------------------------
1346 class serialized(_column):
1347 """ A field able to store an arbitrary python data structure.
1349 Note: only plain components allowed.
1352 def _symbol_set_struct(val):
1353 return simplejson.dumps(val)
1355 def _symbol_get_struct(self, val):
1356 return simplejson.loads(val or '{}')
1359 _type = 'serialized'
1362 _symbol_f = _symbol_set_struct
1363 _symbol_set = (_symbol_c, _symbol_f)
1364 _symbol_get = _symbol_get_struct
1366 # TODO: review completly this class for speed improvement
1367 class property(function):
1369 def _get_default(self, obj, cr, uid, prop_name, context=None):
1370 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1372 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1373 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1375 :param list of string prop_names: list of name of property fields for those we want the default value
1376 :return: map of property field names to their default value
1379 prop = obj.pool.get('ir.property')
1381 for prop_name in prop_names:
1382 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1385 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1386 prop = obj.pool.get('ir.property')
1387 vids = [obj._name + ',' + str(oid) for oid in ids]
1389 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1390 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1392 domain = [('res_id', 'in', vids)] + domain
1393 return prop.search(cr, uid, domain, context=context)
1395 # TODO: to rewrite more clean
1396 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1400 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1402 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1404 default_val = self._get_default(obj, cr, uid, prop_name, context)
1406 property_create = False
1407 if isinstance(default_val, openerp.osv.orm.browse_record):
1408 if default_val.id != id_val:
1409 property_create = True
1410 elif default_val != id_val:
1411 property_create = True
1414 def_id = self._field_get(cr, uid, obj._name, prop_name)
1415 company = obj.pool.get('res.company')
1416 cid = company._company_default_get(cr, uid, obj._name, def_id,
1418 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1420 prop = obj.pool.get('ir.property')
1421 return prop.create(cr, uid, {
1422 'name': propdef.name,
1424 'res_id': obj._name+','+str(id),
1426 'fields_id': def_id,
1431 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1432 prop = obj.pool.get('ir.property')
1433 # get the default values (for res_id = False) for the property fields
1434 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1436 # build the dictionary that will be returned
1439 res[id] = default_val.copy()
1441 for prop_name in prop_names:
1442 property_field = obj._all_columns.get(prop_name).column
1443 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1444 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1445 # in order to make a name_get in batch for all the ids needed.
1448 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1449 obj_reference = obj._name + ',' + str(id)
1450 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1452 res[id][prop_name] = value
1453 # Check existence as root (as seeing the name of a related
1454 # object depends on access right of source document,
1455 # not target, so user may not have access) in order to avoid
1456 # pointing on an unexisting record.
1457 if property_destination_obj:
1458 if res[id][prop_name] and obj.pool[property_destination_obj].exists(cr, SUPERUSER_ID, res[id][prop_name].id):
1459 name_get_ids[id] = res[id][prop_name].id
1461 res[id][prop_name] = False
1462 if property_destination_obj:
1463 # name_get as root (as seeing the name of a related
1464 # object depends on access right of source document,
1465 # not target, so user may not have access.)
1466 name_get_values = dict(obj.pool[property_destination_obj].name_get(cr, SUPERUSER_ID, name_get_ids.values(), context=context))
1467 # the property field is a m2o, we need to return a tuple with (id, name)
1468 for k, v in name_get_ids.iteritems():
1469 if res[k][prop_name]:
1470 res[k][prop_name] = (v , name_get_values.get(v))
1473 def _field_get(self, cr, uid, model_name, prop):
1474 if not self.field_id.get(cr.dbname):
1475 cr.execute('SELECT id \
1476 FROM ir_model_fields \
1477 WHERE name=%s AND model=%s', (prop, model_name))
1479 self.field_id[cr.dbname] = res and res[0]
1480 return self.field_id[cr.dbname]
1482 def __init__(self, obj_prop, **args):
1483 # TODO remove obj_prop parameter (use many2one type)
1485 function.__init__(self, self._fnct_read, False, self._fnct_write,
1486 obj_prop, multi='properties', **args)
1492 def field_to_dict(model, cr, user, field, context=None):
1493 """ Return a dictionary representation of a field.
1495 The string, help, and selection attributes (if any) are untranslated. This
1496 representation is the one returned by fields_get() (fields_get() will do
1501 res = {'type': field._type}
1502 # some attributes for m2m/function field are added as debug info only
1503 if isinstance(field, function):
1504 res['function'] = field._fnct and field._fnct.func_name or False
1505 res['store'] = field.store
1506 if isinstance(field.store, dict):
1507 res['store'] = str(field.store)
1508 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1509 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1510 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1511 if isinstance(field, many2many):
1512 (table, col1, col2) = field._sql_names(model)
1513 res['m2m_join_columns'] = [col1, col2]
1514 res['m2m_join_table'] = table
1515 for arg in ('string', 'readonly', 'states', 'size', 'group_operator', 'required',
1516 'change_default', 'translate', 'help', 'select', 'selectable', 'groups',
1517 'deprecated', 'digits', 'invisible', 'filters'):
1518 if getattr(field, arg, None):
1519 res[arg] = getattr(field, arg)
1521 if hasattr(field, 'selection'):
1522 if isinstance(field.selection, (tuple, list)):
1523 res['selection'] = field.selection
1525 # call the 'dynamic selection' function
1526 res['selection'] = field.selection(model, cr, user, context)
1527 if res['type'] in ('one2many', 'many2many', 'many2one'):
1528 res['relation'] = field._obj
1529 res['domain'] = field._domain(model) if callable(field._domain) else field._domain
1530 res['context'] = field._context
1532 if isinstance(field, one2many):
1533 res['relation_field'] = field._fields_id
1538 class column_info(object):
1539 """ Struct containing details about an osv column, either one local to
1540 its model, or one inherited via _inherits.
1546 .. attribute:: column
1548 column instance, subclass of :class:`_column`
1550 .. attribute:: parent_model
1552 if the column is inherited, name of the model that contains it,
1553 ``None`` for local columns.
1555 .. attribute:: parent_column
1557 the name of the column containing the m2o relationship to the
1558 parent model that contains this column, ``None`` for local columns.
1560 .. attribute:: original_parent
1562 if the column is inherited, name of the original parent model that
1563 contains it i.e in case of multilevel inheritance, ``None`` for
1566 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1568 self.column = column
1569 self.parent_model = parent_model
1570 self.parent_column = parent_column
1571 self.original_parent = original_parent
1574 return '%s(%s, %s, %s, %s, %s)' % (
1575 self.__class__.__name__, self.name, self.column,
1576 self.parent_model, self.parent_column, self.original_parent)
1578 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: