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
114 self.selectable = True
115 self.group_operator = args.get('group_operator', False)
116 self.groups = False # CSV list of ext IDs of groups that can access this field
117 self.deprecated = False # Optional deprecation warning
119 setattr(self, a, args[a])
124 def set(self, cr, obj, id, name, value, user=None, context=None):
125 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
127 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
128 raise Exception(_('undefined get method !'))
130 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
131 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
132 res = obj.read(cr, uid, ids, [name], context=context)
133 return [x[name] for x in res]
135 def as_display_name(self, cr, uid, obj, value, context=None):
136 """Converts a field value to a suitable string representation for a record,
137 e.g. when this field is used as ``rec_name``.
139 :param obj: the ``BaseModel`` instance this column belongs to
140 :param value: a proper value as returned by :py:meth:`~openerp.orm.osv.BaseModel.read`
143 # delegated to class method, so a column type A can delegate
144 # to a column type B.
145 return self._as_display_name(self, cr, uid, obj, value, context=None)
148 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
149 # This needs to be a class method, in case a column type A as to delegate
150 # to a column type B.
151 return tools.ustr(value)
153 # ---------------------------------------------------------
155 # ---------------------------------------------------------
156 class boolean(_column):
159 _symbol_f = lambda x: x and 'True' or 'False'
160 _symbol_set = (_symbol_c, _symbol_f)
162 def __init__(self, string='unknown', required=False, **args):
163 super(boolean, self).__init__(string=string, required=required, **args)
166 "required=True is deprecated: making a boolean field"
167 " `required` has no effect, as NULL values are "
168 "automatically turned into False. args: %r",args)
170 class integer(_column):
173 _symbol_f = lambda x: int(x or 0)
174 _symbol_set = (_symbol_c, _symbol_f)
175 _symbol_get = lambda self,x: x or 0
177 def __init__(self, string='unknown', required=False, **args):
178 super(integer, self).__init__(string=string, required=required, **args)
180 class reference(_column):
182 _classic_read = False # post-process to handle missing target
184 def __init__(self, string, selection, size=None, **args):
185 _column.__init__(self, string=string, size=size, selection=selection, **args)
187 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
189 # copy initial values fetched previously.
191 result[value['id']] = value[name]
193 model, res_id = value[name].split(',')
194 if not obj.pool[model].exists(cr, uid, [int(res_id)], context=context):
195 result[value['id']] = False
199 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
201 # reference fields have a 'model,id'-like value, that we need to convert
203 model_name, res_id = value.split(',')
204 if model_name in obj.pool and res_id:
205 model = obj.pool[model_name]
206 names = model.name_get(cr, uid, [int(res_id)], context=context)
207 return names[0][1] if names else False
208 return tools.ustr(value)
210 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
211 def _symbol_set_char(self, symb):
214 # * we need to remove the "symb==False" from the next line BUT
215 # for now too many things rely on this broken behavior
216 # * the symb==None test should be common to all data types
217 if symb is None or symb == False:
220 # we need to convert the string to a unicode object to be able
221 # to evaluate its length (and possibly truncate it) reliably
222 u_symb = tools.ustr(symb)
223 return u_symb[:self.size].encode('utf8')
228 def __init__(self, string="unknown", size=None, **args):
229 _column.__init__(self, string=string, size=size or None, **args)
230 # self._symbol_set_char defined to keep the backward compatibility
231 self._symbol_f = self._symbol_set_char = lambda x: _symbol_set_char(self, x)
232 self._symbol_set = (self._symbol_c, self._symbol_f)
242 if x is None or x == False:
244 return html_sanitize(x)
246 _symbol_set = (_symbol_c, _symbol_f)
250 class float(_column):
253 _symbol_f = lambda x: __builtin__.float(x or 0.0)
254 _symbol_set = (_symbol_c, _symbol_f)
255 _symbol_get = lambda self,x: x or 0.0
257 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
258 _column.__init__(self, string=string, required=required, **args)
260 # synopsis: digits_compute(cr) -> (precision, scale)
261 self.digits_compute = digits_compute
263 def digits_change(self, cr):
264 if self.digits_compute:
265 self.digits = self.digits_compute(cr)
267 precision, scale = self.digits
268 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
269 precision_digits=scale),
270 precision_digits=scale))
292 """ Returns the current date in a format fit for being a
293 default value to a ``date`` field.
295 This method should be provided as is to the _defaults dict, it
296 should not be called.
298 return DT.date.today().strftime(
299 tools.DEFAULT_SERVER_DATE_FORMAT)
302 def context_today(model, cr, uid, context=None, timestamp=None):
303 """Returns the current date as seen in the client's timezone
304 in a format fit for date fields.
305 This method may be passed as value to initialize _defaults.
307 :param Model model: model (osv) for which the date value is being
308 computed - automatically passed when used in
310 :param datetime timestamp: optional datetime value to use instead of
311 the current date and time (must be a
312 datetime, regular dates can't be converted
314 :param dict context: the 'tz' key in the context should give the
315 name of the User/Client timezone (otherwise
319 today = timestamp or DT.datetime.now()
321 if context and context.get('tz'):
322 tz_name = context['tz']
324 tz_name = model.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
327 utc = pytz.timezone('UTC')
328 context_tz = pytz.timezone(tz_name)
329 utc_today = utc.localize(today, is_dst=False) # UTC = no DST
330 context_today = utc_today.astimezone(context_tz)
332 _logger.debug("failed to compute context/client-specific today date, "
333 "using the UTC value for `today`",
335 return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
337 class datetime(_column):
357 """ Returns the current datetime in a format fit for being a
358 default value to a ``datetime`` field.
360 This method should be provided as is to the _defaults dict, it
361 should not be called.
363 return DT.datetime.now().strftime(
364 tools.DEFAULT_SERVER_DATETIME_FORMAT)
367 def context_timestamp(cr, uid, timestamp, context=None):
368 """Returns the given timestamp converted to the client's timezone.
369 This method is *not* meant for use as a _defaults initializer,
370 because datetime fields are automatically converted upon
371 display on client side. For _defaults you :meth:`fields.datetime.now`
372 should be used instead.
374 :param datetime timestamp: naive datetime value (expressed in UTC)
375 to be converted to the client timezone
376 :param dict context: the 'tz' key in the context should give the
377 name of the User/Client timezone (otherwise
380 :return: timestamp converted to timezone-aware datetime in context
383 assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
384 if context and context.get('tz'):
385 tz_name = context['tz']
387 registry = openerp.modules.registry.RegistryManager.get(cr.dbname)
388 tz_name = registry.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
391 utc = pytz.timezone('UTC')
392 context_tz = pytz.timezone(tz_name)
393 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
394 return utc_timestamp.astimezone(context_tz)
396 _logger.debug("failed to compute context/client-specific timestamp, "
397 "using the UTC value",
401 class binary(_column):
405 # Binary values may be byte strings (python 2.6 byte array), but
406 # the legacy OpenERP convention is to transfer and store binaries
407 # as base64-encoded strings. The base64 string may be provided as a
408 # unicode in some circumstances, hence the str() cast in symbol_f.
409 # This str coercion will only work for pure ASCII unicode strings,
410 # on purpose - non base64 data must be passed as a 8bit byte strings.
411 _symbol_f = lambda symb: symb and Binary(str(symb)) or None
413 _symbol_set = (_symbol_c, _symbol_f)
414 _symbol_get = lambda self, x: x and str(x)
416 _classic_read = False
419 def __init__(self, string='unknown', filters=None, **args):
420 _column.__init__(self, string=string, **args)
421 self.filters = filters
423 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
436 # If client is requesting only the size of the field, we return it instead
437 # of the content. Presumably a separate request will be done to read the actual
438 # content if it's needed at some point.
439 # TODO: after 6.0 we should consider returning a dict with size and content instead of
440 # having an implicit convention for the value
441 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
442 res[i] = tools.human_size(long(val))
447 class selection(_column):
450 def __init__(self, selection, string='unknown', **args):
451 _column.__init__(self, string=string, **args)
452 self.selection = selection
454 # ---------------------------------------------------------
456 # ---------------------------------------------------------
459 # Values: (0, 0, { fields }) create
460 # (1, ID, { fields }) update
461 # (2, ID) remove (delete)
462 # (3, ID) unlink one (target id or target of relation)
464 # (5) unlink all (only valid for one2many)
467 class many2one(_column):
468 _classic_read = False
469 _classic_write = True
472 _symbol_f = lambda x: x or None
473 _symbol_set = (_symbol_c, _symbol_f)
475 def __init__(self, obj, string='unknown', auto_join=False, **args):
476 _column.__init__(self, string=string, **args)
478 self._auto_join = auto_join
480 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
488 res[r['id']] = r[name]
490 res.setdefault(id, '')
491 obj = obj.pool[self._obj]
493 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
494 # we use uid=1 because the visibility of a many2one field value (just id and name)
495 # must be the access right of the parent form and not the linked object itself.
496 records = dict(obj.name_get(cr, SUPERUSER_ID,
497 list(set([x for x in res.values() if isinstance(x, (int,long))])),
500 if res[id] in records:
501 res[id] = (res[id], records[res[id]])
506 def set(self, cr, obj_src, id, field, values, user=None, context=None):
509 obj = obj_src.pool[self._obj]
510 self._table = obj._table
511 if type(values) == type([]):
514 id_new = obj.create(cr, act[2])
515 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
517 obj.write(cr, [act[1]], act[2], context=context)
519 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
520 elif act[0] == 3 or act[0] == 5:
521 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
523 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
526 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
528 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
530 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
531 return obj.pool[self._obj].search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
535 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
536 return value[1] if isinstance(value, tuple) else tools.ustr(value)
539 class one2many(_column):
540 _classic_read = False
541 _classic_write = False
545 def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args):
546 _column.__init__(self, string=string, **args)
548 self._fields_id = fields_id
550 self._auto_join = auto_join
551 #one2many can't be used as condition for defaults
552 assert(self.change_default != True)
554 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
558 context = context.copy()
559 context.update(self._context)
567 domain = self._domain(obj) if callable(self._domain) else self._domain
568 model = obj.pool[self._obj]
569 ids2 = model.search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
570 for r in model._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
571 if r[self._fields_id] in res:
572 res[r[self._fields_id]].append(r['id'])
575 def set(self, cr, obj, id, field, values, user=None, context=None):
580 context = context.copy()
581 context.update(self._context)
582 context['no_store_function'] = True
585 obj = obj.pool[self._obj]
589 act[2][self._fields_id] = id
590 id_new = obj.create(cr, user, act[2], context=context)
591 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
593 obj.write(cr, user, [act[1]], act[2], context=context)
595 obj.unlink(cr, user, [act[1]], context=context)
597 reverse_rel = obj._all_columns.get(self._fields_id)
598 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
599 # if the model has on delete cascade, just delete the row
600 if reverse_rel.column.ondelete == "cascade":
601 obj.unlink(cr, user, [act[1]], context=context)
603 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
605 cr.execute("select 1 from {0} where id=%s and {1}=%s".format(_table, self._fields_id), (act[1], id))
606 if not cr.fetchone():
607 # Must use write() to recompute parent_store structure if needed and check access rules
608 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
610 reverse_rel = obj._all_columns.get(self._fields_id)
611 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
612 # if the o2m has a static domain we must respect it when unlinking
613 domain = self._domain(obj) if callable(self._domain) else self._domain
614 extra_domain = domain or []
615 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
616 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
617 # otherwise we only nullify the reverse foreign key column.
618 if reverse_rel.column.ondelete == "cascade":
619 obj.unlink(cr, user, ids_to_unlink, context=context)
621 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
623 # Must use write() to recompute parent_store structure if needed
624 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
626 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
627 ids3 = map(lambda x:x[0], cr.fetchall())
628 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
631 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
632 domain = self._domain(obj) if callable(self._domain) else self._domain
633 return obj.pool[self._obj].name_search(cr, uid, value, domain, operator, context=context,limit=limit)
637 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
638 raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)')
641 # Values: (0, 0, { fields }) create
642 # (1, ID, { fields }) update (write fields to ID)
643 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
644 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
645 # (4, ID) link (add a relationship)
647 # (6, ?, ids) set a list of links
649 class many2many(_column):
650 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
651 low-level details of the intermediary relationship table transparently.
652 A many-to-many relationship is always symmetrical, and can be declared and accessed
653 from either endpoint model.
654 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
655 or id2 (destination foreign key column name) are not specified, the system will
656 provide default values. This will by default only allow one single symmetrical
657 many-to-many relationship between the source and destination model.
658 For multiple many-to-many relationship between the same models and for
659 relationships where source and destination models are the same, ``rel``, ``id1``
660 and ``id2`` should be specified explicitly.
662 :param str obj: destination model
663 :param str rel: optional name of the intermediary relationship table. If not specified,
664 a canonical name will be derived based on the alphabetically-ordered
665 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
666 Automatic naming is not possible when the source and destination are
667 the same, for obvious ambiguity reasons.
668 :param str id1: optional name for the column holding the foreign key to the current
669 model in the relationship table. If not specified, a canonical name
670 will be derived based on the model name (in the form: `src_model_id`).
671 :param str id2: optional name for the column holding the foreign key to the destination
672 model in the relationship table. If not specified, a canonical name
673 will be derived based on the model name (in the form: `dest_model_id`)
674 :param str string: field label
676 _classic_read = False
677 _classic_write = False
681 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
684 _column.__init__(self, string=string, **args)
686 if rel and '.' in rel:
687 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
688 'You used %s, which is not a valid SQL table name.')% (string,rel))
694 def _sql_names(self, source_model):
695 """Return the SQL names defining the structure of the m2m relationship table
697 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
698 local_col is the name of the column holding the current model's FK, and
699 dest_col is the name of the column holding the destination model's FK, and
701 tbl, col1, col2 = self._rel, self._id1, self._id2
702 if not all((tbl, col1, col2)):
703 # the default table name is based on the stable alphabetical order of tables
704 dest_model = source_model.pool[self._obj]
705 tables = tuple(sorted([source_model._table, dest_model._table]))
707 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
708 'is not possible when source and destination models are '\
710 tbl = '%s_%s_rel' % tables
712 col1 = '%s_id' % source_model._table
714 col2 = '%s_id' % dest_model._table
715 return tbl, col1, col2
717 def _get_query_and_where_params(self, cr, model, ids, values, where_params):
718 """ Extracted from ``get`` to facilitate fine-tuning of the generated
720 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
721 FROM %(rel)s, %(from_c)s \
722 WHERE %(rel)s.%(id1)s IN %%s \
723 AND %(rel)s.%(id2)s = %(tbl)s.id \
729 return query, where_params
731 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
743 "Specifying offset at a many2many.get() is deprecated and may"
744 " produce unpredictable results.")
745 obj = model.pool[self._obj]
746 rel, id1, id2 = self._sql_names(model)
748 # static domains are lists, and are evaluated both here and on client-side, while string
749 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
750 # FIXME: make this distinction explicit in API!
751 domain = isinstance(self._domain, list) and self._domain or []
753 wquery = obj._where_calc(cr, user, domain, context=context)
754 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
755 from_c, where_c, where_params = wquery.get_sql()
757 where_c = ' AND ' + where_c
759 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
762 if self._limit is not None:
763 limit_str = ' LIMIT %d' % self._limit
765 query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel,
772 'order_by': order_by,
776 cr.execute(query, [tuple(ids),] + where_params)
777 for r in cr.fetchall():
778 res[r[1]].append(r[0])
781 def set(self, cr, model, id, name, values, user=None, context=None):
786 rel, id1, id2 = self._sql_names(model)
787 obj = model.pool[self._obj]
789 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
792 idnew = obj.create(cr, user, act[2], context=context)
793 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
795 obj.write(cr, user, [act[1]], act[2], context=context)
797 obj.unlink(cr, user, [act[1]], context=context)
799 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
801 # following queries are in the same transaction - so should be relatively safe
802 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
803 if not cr.fetchone():
804 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
806 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
809 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
811 d1 = ' and ' + ' and '.join(d1)
814 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)
816 for act_nbr in act[2]:
817 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
820 # TODO: use a name_search
822 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
823 return obj.pool[self._obj].search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
826 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
827 raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)')
830 def get_nice_size(value):
832 if isinstance(value, (int,long)):
834 elif value: # this is supposed to be a string
836 return tools.human_size(size)
838 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
839 # and http://bugs.python.org/issue10066
840 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
842 def sanitize_binary_value(value):
843 # binary fields should be 7-bit ASCII base64-encoded data,
844 # but we do additional sanity checks to make sure the values
845 # are not something else that won't pass via XML-RPC
846 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
847 # these builtin types are meant to pass untouched
850 # Handle invalid bytes values that will cause problems
851 # for XML-RPC. See for more info:
852 # - http://bugs.python.org/issue10066
853 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
855 # Coercing to unicode would normally allow it to properly pass via
856 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
857 # (this works for _any_ byte values, thanks to the fallback
858 # to latin-1 passthrough encoding when decoding to unicode)
859 value = tools.ustr(value)
861 # Due to Python bug #10066 this could still yield invalid XML
862 # bytes, specifically in the low byte range, that will crash
863 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
864 # So check for low bytes values, and if any, perform
865 # base64 encoding - not very smart or useful, but this is
866 # our last resort to avoid crashing the request.
867 if invalid_xml_low_bytes.search(value):
868 # b64-encode after restoring the pure bytes with latin-1
869 # passthrough encoding
870 value = base64.b64encode(value.encode('latin-1'))
875 # ---------------------------------------------------------
877 # ---------------------------------------------------------
878 class function(_column):
880 A field whose value is computed by a function (rather
881 than being read from the database).
883 :param fnct: the callable that will compute the field value.
884 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
885 :param fnct_inv: the callable that will allow writing values in that field
886 (if not provided, the field is read-only).
887 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
889 :param str type: type of the field simulated by the function field
890 :param fnct_search: the callable that allows searching on the field
891 (if not provided, search will not return any result).
892 :param store: store computed value in database
893 (see :ref:`The *store* parameter <field-function-store>`).
894 :type store: True or dict specifying triggers for field computation
895 :param multi: name of batch for batch computation of function fields.
896 All fields with the same batch name will be computed by
897 a single function call. This changes the signature of the
900 .. _field-function-fnct: The ``fnct`` parameter
902 .. rubric:: The ``fnct`` parameter
904 The callable implementing the function field must have the following signature:
906 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
908 Implements the function field.
910 :param orm model: model to which the field belongs (should be ``self`` for
912 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
913 list of field names to compute.
914 :type field_name(s): str | [str]
915 :param arg: arbitrary value passed when declaring the function field
917 :return: mapping of ``ids`` to computed values, or if multi is provided,
918 to a map of field_names to computed values
920 The values in the returned dictionary must be of the type specified by the type
921 argument in the field declaration.
923 Here is an example with a simple function ``char`` function field::
926 def compute(self, cr, uid, ids, field_name, arg, context):
930 _columns['my_char'] = fields.function(compute, type='char', size=50)
932 # when called with ``ids=[1,2,3]``, ``compute`` could return:
936 3: False # null values should be returned explicitly too
939 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
940 of the field names that should be computed. Each value in the returned
941 dictionary must then be a dictionary mapping field names to values.
943 Here is an example where two function fields (``name`` and ``age``)
944 are both computed by a single function field::
947 def compute(self, cr, uid, ids, field_names, arg, context):
951 _columns['name'] = fields.function(compute_person_data, type='char',\
952 size=50, multi='person_data')
953 _columns[''age'] = fields.function(compute_person_data, type='integer',\
956 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
958 1: {'name': 'Bob', 'age': 23},
959 2: {'name': 'Sally', 'age': 19},
960 3: {'name': 'unknown', 'age': False}
963 .. _field-function-fnct-inv:
965 .. rubric:: The ``fnct_inv`` parameter
967 This callable implements the write operation for the function field
968 and must have the following signature:
970 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
972 Callable that implements the ``write`` operation for the function field.
974 :param orm model: model to which the field belongs (should be ``self`` for
976 :param int id: the identifier of the object to write on
977 :param str field_name: name of the field to set
978 :param fnct_inv_arg: arbitrary value passed when declaring the function field
981 When writing values for a function field, the ``multi`` parameter is ignored.
983 .. _field-function-fnct-search:
985 .. rubric:: The ``fnct_search`` parameter
987 This callable implements the search operation for the function field
988 and must have the following signature:
990 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
992 Callable that implements the ``search`` operation for the function field by expanding
993 a search criterion based on the function field into a new domain based only on
994 columns that are stored in the database.
996 :param orm model: model to which the field belongs (should be ``self`` for
998 :param orm model_again: same value as ``model`` (seriously! this is for backwards
1000 :param str field_name: name of the field to search on
1001 :param list criterion: domain component specifying the search criterion on the field.
1003 :return: domain to use instead of ``criterion`` when performing the search.
1004 This new domain must be based only on columns stored in the database, as it
1005 will be used directly without any translation.
1007 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
1008 The most generic way to implement ``fnct_search`` is to directly search for the records that
1009 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
1010 ``[('id','in',[1,3,5])]``.
1012 .. _field-function-store:
1014 .. rubric:: The ``store`` parameter
1016 The ``store`` parameter allows caching the result of the field computation in the
1017 database, and defining the triggers that will invalidate that cache and force a
1018 recomputation of the function field.
1019 When not provided, the field is computed every time its value is read.
1020 The value of ``store`` may be either ``True`` (to recompute the field value whenever
1021 any field in the same record is modified), or a dictionary specifying a more
1022 flexible set of recomputation triggers.
1024 A trigger specification is a dictionary that maps the names of the models that
1025 will trigger the computation, to a tuple describing the trigger rule, in the
1029 'trigger_model': (mapping_function,
1030 ['trigger_field1', 'trigger_field2'],
1034 A trigger rule is defined by a 3-item tuple where:
1036 * The ``mapping_function`` is defined as follows:
1038 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
1040 Callable that maps record ids of a trigger model to ids of the
1041 corresponding records in the source model (whose field values
1042 need to be recomputed).
1044 :param orm model: trigger_model
1045 :param list trigger_ids: ids of the records of trigger_model that were
1048 :return: list of ids of the source model whose function field values
1049 need to be recomputed
1051 * The second item is a list of the fields who should act as triggers for
1052 the computation. If an empty list is given, all fields will act as triggers.
1053 * The last item is the priority, used to order the triggers when processing them
1054 after any write operation on a model that has function field triggers. The
1055 default priority is 10.
1057 In fact, setting store = True is the same as using the following trigger dict::
1060 'model_itself': (lambda self, cr, uid, ids, context: ids,
1066 _classic_read = False
1067 _classic_write = False
1073 # multi: compute several fields in one call
1075 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):
1076 _column.__init__(self, **args)
1079 self._fnct_inv = fnct_inv
1082 if 'relation' in args:
1083 self._obj = args['relation']
1085 self.digits = args.get('digits', (16,2))
1086 self.digits_compute = args.get('digits_compute', None)
1088 self._fnct_inv_arg = fnct_inv_arg
1092 self._fnct_search = fnct_search
1095 if not fnct_search and not store:
1096 self.selectable = False
1099 if self._type != 'many2one':
1100 # m2o fields need to return tuples with name_get, not just foreign keys
1101 self._classic_read = True
1102 self._classic_write = True
1104 self._symbol_get=lambda x:x and str(x)
1106 self._prefetch = True
1109 self._symbol_c = float._symbol_c
1110 self._symbol_f = float._symbol_f
1111 self._symbol_set = float._symbol_set
1113 if type == 'boolean':
1114 self._symbol_c = boolean._symbol_c
1115 self._symbol_f = boolean._symbol_f
1116 self._symbol_set = boolean._symbol_set
1118 if type == 'integer':
1119 self._symbol_c = integer._symbol_c
1120 self._symbol_f = integer._symbol_f
1121 self._symbol_set = integer._symbol_set
1124 self._symbol_c = char._symbol_c
1125 self._symbol_f = lambda x: _symbol_set_char(self, x)
1126 self._symbol_set = (self._symbol_c, self._symbol_f)
1128 def digits_change(self, cr):
1129 if self._type == 'float':
1130 if self.digits_compute:
1131 self.digits = self.digits_compute(cr)
1133 precision, scale = self.digits
1134 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1135 precision_digits=scale),
1136 precision_digits=scale))
1138 def search(self, cr, uid, obj, name, args, context=None):
1139 if not self._fnct_search:
1140 #CHECKME: should raise an exception
1142 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1144 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1148 field_type = obj._columns[field]._type
1149 if field_type == "many2one":
1150 # make the result a tuple if it is not already one
1151 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1152 obj_model = obj.pool[obj._columns[field].relation]
1153 dict_names = dict(obj_model.name_get(cr, SUPERUSER_ID, [value], context))
1154 result = (value, dict_names[value])
1156 if field_type == 'binary':
1157 if context.get('bin_size'):
1158 # client requests only the size of binary fields
1159 result = get_nice_size(value)
1160 elif not context.get('bin_raw'):
1161 result = sanitize_binary_value(value)
1163 if field_type == "integer" and value > xmlrpclib.MAXINT:
1164 # integer/long values greater than 2^31-1 are not supported
1165 # in pure XMLRPC, so we have to pass them as floats :-(
1166 # This is not needed for stored fields and non-functional integer
1167 # fields, as their values are constrained by the database backend
1168 # to the same 32bits signed int limit.
1169 result = __builtin__.float(value)
1172 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1173 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1175 if self._multi and id in result:
1176 for field, value in result[id].iteritems():
1178 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1179 elif result.get(id):
1180 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1183 def set(self, cr, obj, id, name, value, user=None, context=None):
1187 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1190 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
1191 # Function fields are supposed to emulate a basic field type,
1192 # so they can delegate to the basic type for record name rendering
1193 return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
1195 # ---------------------------------------------------------
1197 # ---------------------------------------------------------
1199 class related(function):
1200 """Field that points to some data inside another field of the current record.
1205 'foo_id': fields.many2one('my.foo', 'Foo'),
1206 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1210 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1211 # assume self._arg = ('foo', 'bar', 'baz')
1212 # domain = [(name, op, val)] => search [('foo.bar.baz', op, val)]
1213 field = '.'.join(self._arg)
1214 return map(lambda x: (field, x[1], x[2]), domain)
1216 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1217 if isinstance(ids, (int, long)):
1219 for record in obj.browse(cr, uid, ids, context=context):
1220 # traverse all fields except the last one
1221 for field in self.arg[:-1]:
1222 record = record[field] or False
1225 elif isinstance(record, list):
1226 # record is the result of a one2many or many2many field
1229 # write on the last field
1230 record.write({self.arg[-1]: values})
1232 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1234 for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
1236 for field in self.arg:
1237 if isinstance(value, list):
1239 value = value[field] or False
1242 res[record.id] = value
1244 if self._type == 'many2one':
1245 # res[id] is a browse_record or False; convert it to (id, name) or False.
1246 # Perform name_get as root, as seeing the name of a related object depends on
1247 # access right of source document, not target, so user may not have access.
1248 value_ids = list(set(value.id for value in res.itervalues() if value))
1249 value_name = dict(obj.pool[self._obj].name_get(cr, SUPERUSER_ID, value_ids, context=context))
1250 res = dict((id, value and (value.id, value_name[value.id])) for id, value in res.iteritems())
1252 elif self._type in ('one2many', 'many2many'):
1253 # res[id] is a list of browse_record or False; convert it to a list of ids
1254 res = dict((id, value and map(int, value) or []) for id, value in res.iteritems())
1258 def __init__(self, *arg, **args):
1260 self._relations = []
1261 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1262 if self.store is True:
1263 # TODO: improve here to change self.store = {...} according to related objects
1267 class sparse(function):
1269 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1271 + For a many2many field, a list of tuples is expected.
1272 Here is the list of tuple that are accepted, with the corresponding semantics ::
1274 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1275 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1276 (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)
1277 (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)
1278 (4, ID) link to existing record with id = ID (adds a relationship)
1279 (5) unlink all (like using (3,ID) for all linked records)
1280 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1283 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1285 + For a one2many field, a lits of tuples is expected.
1286 Here is the list of tuple that are accepted, with the corresponding semantics ::
1288 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1289 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1290 (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)
1293 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1296 if self._type == 'many2many':
1297 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1300 elif self._type == 'one2many':
1303 relation_obj = obj.pool[self.relation]
1305 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1307 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1309 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1311 relation_obj.unlink(cr, uid, vals[1], context=context)
1312 read_value.remove(vals[1])
1317 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1318 if not type(ids) == list:
1320 records = obj.browse(cr, uid, ids, context=context)
1321 for record in records:
1322 # grab serialized value as object - already deserialized
1323 serialized = getattr(record, self.serialization_field)
1325 # simply delete the key to unset it.
1326 serialized.pop(field_name, None)
1328 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1329 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1332 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1334 records = obj.browse(cr, uid, ids, context=context)
1335 for record in records:
1336 # grab serialized value as object - already deserialized
1337 serialized = getattr(record, self.serialization_field)
1338 results[record.id] = {}
1339 for field_name in field_names:
1340 field_type = obj._columns[field_name]._type
1341 value = serialized.get(field_name, False)
1342 if field_type in ('one2many','many2many'):
1345 # filter out deleted records as superuser
1346 relation_obj = obj.pool[obj._columns[field_name].relation]
1347 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1348 if type(value) in (int,long) and field_type == 'many2one':
1349 relation_obj = obj.pool[obj._columns[field_name].relation]
1350 # check for deleted record as superuser
1351 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1353 results[record.id][field_name] = value
1356 def __init__(self, serialization_field, **kwargs):
1357 self.serialization_field = serialization_field
1358 super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1362 # ---------------------------------------------------------
1364 # ---------------------------------------------------------
1366 class dummy(function):
1367 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1370 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1373 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1376 def __init__(self, *arg, **args):
1378 self._relations = []
1379 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1381 # ---------------------------------------------------------
1383 # ---------------------------------------------------------
1385 class serialized(_column):
1386 """ A field able to store an arbitrary python data structure.
1388 Note: only plain components allowed.
1391 def _symbol_set_struct(val):
1392 return simplejson.dumps(val)
1394 def _symbol_get_struct(self, val):
1395 return simplejson.loads(val or '{}')
1398 _type = 'serialized'
1401 _symbol_f = _symbol_set_struct
1402 _symbol_set = (_symbol_c, _symbol_f)
1403 _symbol_get = _symbol_get_struct
1405 # TODO: review completly this class for speed improvement
1406 class property(function):
1408 def _get_default(self, obj, cr, uid, prop_name, context=None):
1409 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1411 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1412 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1414 :param list of string prop_names: list of name of property fields for those we want the default value
1415 :return: map of property field names to their default value
1418 prop = obj.pool.get('ir.property')
1420 for prop_name in prop_names:
1421 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1424 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1425 prop = obj.pool.get('ir.property')
1426 vids = [obj._name + ',' + str(oid) for oid in ids]
1427 def_id = self._field_get(cr, uid, obj._name, prop_name[0])
1428 company = obj.pool.get('res.company')
1429 cid = company._company_default_get(cr, uid, obj._name, def_id, context=context)
1430 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name), ('company_id', '=', cid)]
1431 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1433 domain = [('res_id', 'in', vids)] + domain
1434 return prop.search(cr, uid, domain, context=context)
1436 # TODO: to rewrite more clean
1437 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1441 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1443 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1445 default_val = self._get_default(obj, cr, uid, prop_name, context)
1447 property_create = False
1448 if isinstance(default_val, openerp.osv.orm.browse_record):
1449 if default_val.id != id_val:
1450 property_create = True
1451 elif default_val != id_val:
1452 property_create = True
1455 def_id = self._field_get(cr, uid, obj._name, prop_name)
1456 company = obj.pool.get('res.company')
1457 cid = company._company_default_get(cr, uid, obj._name, def_id,
1459 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1461 prop = obj.pool.get('ir.property')
1462 return prop.create(cr, uid, {
1463 'name': propdef.name,
1465 'res_id': obj._name+','+str(id),
1467 'fields_id': def_id,
1472 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1473 prop = obj.pool.get('ir.property')
1474 # get the default values (for res_id = False) for the property fields
1475 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1477 # build the dictionary that will be returned
1480 res[id] = default_val.copy()
1482 for prop_name in prop_names:
1483 property_field = obj._all_columns.get(prop_name).column
1484 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1485 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1486 # in order to make a name_get in batch for all the ids needed.
1489 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1490 obj_reference = obj._name + ',' + str(id)
1491 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1493 res[id][prop_name] = value
1494 # Check existence as root (as seeing the name of a related
1495 # object depends on access right of source document,
1496 # not target, so user may not have access) in order to avoid
1497 # pointing on an unexisting record.
1498 if property_destination_obj:
1499 if res[id][prop_name] and obj.pool[property_destination_obj].exists(cr, SUPERUSER_ID, res[id][prop_name].id):
1500 name_get_ids[id] = res[id][prop_name].id
1502 res[id][prop_name] = False
1503 if property_destination_obj:
1504 # name_get as root (as seeing the name of a related
1505 # object depends on access right of source document,
1506 # not target, so user may not have access.)
1507 name_get_values = dict(obj.pool[property_destination_obj].name_get(cr, SUPERUSER_ID, name_get_ids.values(), context=context))
1508 # the property field is a m2o, we need to return a tuple with (id, name)
1509 for k, v in name_get_ids.iteritems():
1510 if res[k][prop_name]:
1511 res[k][prop_name] = (v , name_get_values.get(v))
1514 def _field_get(self, cr, uid, model_name, prop):
1515 if not self.field_id.get(cr.dbname):
1516 cr.execute('SELECT id \
1517 FROM ir_model_fields \
1518 WHERE name=%s AND model=%s', (prop, model_name))
1520 self.field_id[cr.dbname] = res and res[0]
1521 return self.field_id[cr.dbname]
1524 def __init__(self, **args):
1526 if 'view_load' in args:
1527 _logger.warning("view_load attribute is deprecated on ir.fields. Args: %r", args)
1528 obj = 'relation' in args and args['relation'] or ''
1529 function.__init__(self, self._fnct_read, False, self._fnct_write, obj=obj, multi='properties', **args)
1535 def field_to_dict(model, cr, user, field, context=None):
1536 """ Return a dictionary representation of a field.
1538 The string, help, and selection attributes (if any) are untranslated. This
1539 representation is the one returned by fields_get() (fields_get() will do
1544 res = {'type': field._type}
1545 # some attributes for m2m/function field are added as debug info only
1546 if isinstance(field, function):
1547 res['function'] = field._fnct and field._fnct.func_name or False
1548 res['store'] = field.store
1549 if isinstance(field.store, dict):
1550 res['store'] = str(field.store)
1551 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1552 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1553 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1554 if isinstance(field, many2many):
1555 (table, col1, col2) = field._sql_names(model)
1556 res['m2m_join_columns'] = [col1, col2]
1557 res['m2m_join_table'] = table
1558 for arg in ('string', 'readonly', 'states', 'size', 'group_operator', 'required',
1559 'change_default', 'translate', 'help', 'select', 'selectable', 'groups',
1560 'deprecated', 'digits', 'invisible', 'filters'):
1561 if getattr(field, arg, None):
1562 res[arg] = getattr(field, arg)
1564 if hasattr(field, 'selection'):
1565 if isinstance(field.selection, (tuple, list)):
1566 res['selection'] = field.selection
1568 # call the 'dynamic selection' function
1569 res['selection'] = field.selection(model, cr, user, context)
1570 if res['type'] in ('one2many', 'many2many', 'many2one'):
1571 res['relation'] = field._obj
1572 res['domain'] = field._domain(model) if callable(field._domain) else field._domain
1573 res['context'] = field._context
1575 if isinstance(field, one2many):
1576 res['relation_field'] = field._fields_id
1581 class column_info(object):
1582 """ Struct containing details about an osv column, either one local to
1583 its model, or one inherited via _inherits.
1589 .. attribute:: column
1591 column instance, subclass of :class:`_column`
1593 .. attribute:: parent_model
1595 if the column is inherited, name of the model that contains it,
1596 ``None`` for local columns.
1598 .. attribute:: parent_column
1600 the name of the column containing the m2o relationship to the
1601 parent model that contains this column, ``None`` for local columns.
1603 .. attribute:: original_parent
1605 if the column is inherited, name of the original parent model that
1606 contains it i.e in case of multilevel inheritance, ``None`` for
1609 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1611 self.column = column
1612 self.parent_model = parent_model
1613 self.parent_column = parent_column
1614 self.original_parent = original_parent
1617 return '%s(%s, %s, %s, %s, %s)' % (
1618 self.__class__.__name__, self.name, self.column,
1619 self.parent_model, self.parent_column, self.original_parent)
1621 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: