1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 - relations (one2many, many2one, many2many)
28 * _classic_read: is a classic sql fields
41 from psycopg2 import Binary
44 import openerp.tools as tools
45 from openerp.tools.translate import _
46 from openerp.tools import float_round, float_repr
48 from openerp.tools.html_sanitize import html_sanitize
49 from openerp import SUPERUSER_ID
51 _logger = logging.getLogger(__name__)
53 def _symbol_set(symb):
54 if symb is None or symb == False:
56 elif isinstance(symb, unicode):
57 return symb.encode('utf-8')
61 class _column(object):
62 """ Base of all fields, a database column
64 An instance of this object is a *description* of a database column. It will
65 not hold any data, but only provide the methods to manipulate data of an
66 ORM record or even prepare/update the database to hold such a field of data.
76 _symbol_f = _symbol_set
77 _symbol_set = (_symbol_c, _symbol_f)
80 # used to hide a certain field type in the list of field types
83 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):
86 The 'manual' keyword argument specifies if the field is a custom one.
87 It corresponds to the 'state' column in ir_model_fields.
94 self.states = states or {}
96 self.readonly = readonly
97 self.required = required
99 self.help = args.get('help', '')
100 self.priority = priority
101 self.change_default = change_default
102 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
103 self.translate = translate
104 self._domain = domain
105 self._context = context
111 self.selectable = True
112 self.group_operator = args.get('group_operator', False)
113 self.groups = False # CSV list of ext IDs of groups that can access this field
114 self.deprecated = False # Optional deprecation warning
117 setattr(self, a, args[a])
122 def set(self, cr, obj, id, name, value, user=None, context=None):
123 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
125 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
126 raise Exception(_('undefined get method !'))
128 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
129 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
130 res = obj.read(cr, uid, ids, [name], context=context)
131 return [x[name] for x in res]
133 def as_display_name(self, cr, uid, obj, value, context=None):
134 """Converts a field value to a suitable string representation for a record,
135 e.g. when this field is used as ``rec_name``.
137 :param obj: the ``BaseModel`` instance this column belongs to
138 :param value: a proper value as returned by :py:meth:`~openerp.orm.osv.BaseModel.read`
141 # delegated to class method, so a column type A can delegate
142 # to a column type B.
143 return self._as_display_name(self, cr, uid, obj, value, context=None)
146 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
147 # This needs to be a class method, in case a column type A as to delegate
148 # to a column type B.
149 return tools.ustr(value)
151 # ---------------------------------------------------------
153 # ---------------------------------------------------------
154 class boolean(_column):
157 _symbol_f = lambda x: x and 'True' or 'False'
158 _symbol_set = (_symbol_c, _symbol_f)
160 def __init__(self, string='unknown', required=False, **args):
161 super(boolean, self).__init__(string=string, required=required, **args)
164 "required=True is deprecated: making a boolean field"
165 " `required` has no effect, as NULL values are "
166 "automatically turned into False.")
168 class integer(_column):
171 _symbol_f = lambda x: int(x or 0)
172 _symbol_set = (_symbol_c, _symbol_f)
173 _symbol_get = lambda self,x: x or 0
175 def __init__(self, string='unknown', required=False, **args):
176 super(integer, self).__init__(string=string, required=required, **args)
178 class reference(_column):
180 _classic_read = False # post-process to handle missing target
182 def __init__(self, string, selection, size, **args):
183 _column.__init__(self, string=string, size=size, selection=selection, **args)
185 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
187 # copy initial values fetched previously.
189 result[value['id']] = value[name]
191 model, res_id = value[name].split(',')
192 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
193 result[value['id']] = False
197 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
199 # reference fields have a 'model,id'-like value, that we need to convert
201 model_name, res_id = value.split(',')
202 model = obj.pool.get(model_name)
204 return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
205 return tools.ustr(value)
210 def __init__(self, string="unknown", size=None, **args):
211 _column.__init__(self, string=string, size=size or None, **args)
212 self._symbol_set = (self._symbol_c, self._symbol_set_char)
214 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
215 def _symbol_set_char(self, symb):
217 # * we need to remove the "symb==False" from the next line BUT
218 # for now too many things rely on this broken behavior
219 # * the symb==None test should be common to all data types
220 if symb is None or symb == False:
223 # we need to convert the string to a unicode object to be able
224 # to evaluate its length (and possibly truncate it) reliably
225 u_symb = tools.ustr(symb)
227 return u_symb[:self.size].encode('utf8')
237 return html_sanitize(x)
239 _symbol_set = (_symbol_c, _symbol_f)
243 class float(_column):
246 _symbol_f = lambda x: __builtin__.float(x or 0.0)
247 _symbol_set = (_symbol_c, _symbol_f)
248 _symbol_get = lambda self,x: x or 0.0
250 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
251 _column.__init__(self, string=string, required=required, **args)
253 # synopsis: digits_compute(cr) -> (precision, scale)
254 self.digits_compute = digits_compute
256 def digits_change(self, cr):
257 if self.digits_compute:
258 self.digits = self.digits_compute(cr)
260 precision, scale = self.digits
261 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
262 precision_digits=scale),
263 precision_digits=scale))
270 """ Returns the current date in a format fit for being a
271 default value to a ``date`` field.
273 This method should be provided as is to the _defaults dict, it
274 should not be called.
276 return DT.date.today().strftime(
277 tools.DEFAULT_SERVER_DATE_FORMAT)
280 def context_today(model, cr, uid, context=None, timestamp=None):
281 """Returns the current date as seen in the client's timezone
282 in a format fit for date fields.
283 This method may be passed as value to initialize _defaults.
285 :param Model model: model (osv) for which the date value is being
286 computed - technical field, currently ignored,
287 automatically passed when used in _defaults.
288 :param datetime timestamp: optional datetime value to use instead of
289 the current date and time (must be a
290 datetime, regular dates can't be converted
292 :param dict context: the 'tz' key in the context should give the
293 name of the User/Client timezone (otherwise
297 today = timestamp or DT.datetime.now()
299 if context and context.get('tz'):
301 utc = pytz.timezone('UTC')
302 context_tz = pytz.timezone(context['tz'])
303 utc_today = utc.localize(today, is_dst=False) # UTC = no DST
304 context_today = utc_today.astimezone(context_tz)
306 _logger.debug("failed to compute context/client-specific today date, "
307 "using the UTC value for `today`",
309 return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
311 class datetime(_column):
315 """ Returns the current datetime in a format fit for being a
316 default value to a ``datetime`` field.
318 This method should be provided as is to the _defaults dict, it
319 should not be called.
321 return DT.datetime.now().strftime(
322 tools.DEFAULT_SERVER_DATETIME_FORMAT)
325 def context_timestamp(cr, uid, timestamp, context=None):
326 """Returns the given timestamp converted to the client's timezone.
327 This method is *not* meant for use as a _defaults initializer,
328 because datetime fields are automatically converted upon
329 display on client side. For _defaults you :meth:`fields.datetime.now`
330 should be used instead.
332 :param datetime timestamp: naive datetime value (expressed in UTC)
333 to be converted to the client timezone
334 :param dict context: the 'tz' key in the context should give the
335 name of the User/Client timezone (otherwise
338 :return: timestamp converted to timezone-aware datetime in context
341 assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
342 if context and context.get('tz'):
344 utc = pytz.timezone('UTC')
345 context_tz = pytz.timezone(context['tz'])
346 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
347 return utc_timestamp.astimezone(context_tz)
349 _logger.debug("failed to compute context/client-specific timestamp, "
350 "using the UTC value",
354 class binary(_column):
358 # Binary values may be byte strings (python 2.6 byte array), but
359 # the legacy OpenERP convention is to transfer and store binaries
360 # as base64-encoded strings. The base64 string may be provided as a
361 # unicode in some circumstances, hence the str() cast in symbol_f.
362 # This str coercion will only work for pure ASCII unicode strings,
363 # on purpose - non base64 data must be passed as a 8bit byte strings.
364 _symbol_f = lambda symb: symb and Binary(str(symb)) or None
366 _symbol_set = (_symbol_c, _symbol_f)
367 _symbol_get = lambda self, x: x and str(x)
369 _classic_read = False
372 def __init__(self, string='unknown', filters=None, **args):
373 _column.__init__(self, string=string, **args)
374 self.filters = filters
376 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
389 # If client is requesting only the size of the field, we return it instead
390 # of the content. Presumably a separate request will be done to read the actual
391 # content if it's needed at some point.
392 # TODO: after 6.0 we should consider returning a dict with size and content instead of
393 # having an implicit convention for the value
394 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
395 res[i] = tools.human_size(long(val))
400 class selection(_column):
403 def __init__(self, selection, string='unknown', **args):
404 _column.__init__(self, string=string, **args)
405 self.selection = selection
407 # ---------------------------------------------------------
409 # ---------------------------------------------------------
412 # Values: (0, 0, { fields }) create
413 # (1, ID, { fields }) update
414 # (2, ID) remove (delete)
415 # (3, ID) unlink one (target id or target of relation)
417 # (5) unlink all (only valid for one2many)
420 class many2one(_column):
421 _classic_read = False
422 _classic_write = True
425 _symbol_f = lambda x: x or None
426 _symbol_set = (_symbol_c, _symbol_f)
428 def __init__(self, obj, string='unknown', **args):
429 _column.__init__(self, string=string, **args)
432 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
440 res[r['id']] = r[name]
442 res.setdefault(id, '')
443 obj = obj.pool.get(self._obj)
445 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
446 # we use uid=1 because the visibility of a many2one field value (just id and name)
447 # must be the access right of the parent form and not the linked object itself.
448 records = dict(obj.name_get(cr, SUPERUSER_ID,
449 list(set([x for x in res.values() if isinstance(x, (int,long))])),
452 if res[id] in records:
453 res[id] = (res[id], records[res[id]])
458 def set(self, cr, obj_src, id, field, values, user=None, context=None):
461 obj = obj_src.pool.get(self._obj)
462 self._table = obj_src.pool.get(self._obj)._table
463 if type(values) == type([]):
466 id_new = obj.create(cr, act[2])
467 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
469 obj.write(cr, [act[1]], act[2], context=context)
471 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
472 elif act[0] == 3 or act[0] == 5:
473 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
475 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
478 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
480 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
482 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
483 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
487 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
488 return value[1] if isinstance(value, tuple) else tools.ustr(value)
491 class one2many(_column):
492 _classic_read = False
493 _classic_write = False
497 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
498 _column.__init__(self, string=string, **args)
500 self._fields_id = fields_id
502 #one2many can't be used as condition for defaults
503 assert(self.change_default != True)
505 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
509 context = context.copy()
510 context.update(self._context)
518 domain = self._domain(obj) if callable(self._domain) else self._domain
519 ids2 = obj.pool.get(self._obj).search(cr, user, domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
520 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
521 if r[self._fields_id] in res:
522 res[r[self._fields_id]].append(r['id'])
525 def set(self, cr, obj, id, field, values, user=None, context=None):
530 context = context.copy()
531 context.update(self._context)
532 context['no_store_function'] = True
535 _table = obj.pool.get(self._obj)._table
536 obj = obj.pool.get(self._obj)
539 act[2][self._fields_id] = id
540 id_new = obj.create(cr, user, act[2], context=context)
541 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
543 obj.write(cr, user, [act[1]], act[2], context=context)
545 obj.unlink(cr, user, [act[1]], context=context)
547 reverse_rel = obj._all_columns.get(self._fields_id)
548 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
549 # if the model has on delete cascade, just delete the row
550 if reverse_rel.column.ondelete == "cascade":
551 obj.unlink(cr, user, [act[1]], context=context)
553 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
555 # Must use write() to recompute parent_store structure if needed
556 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
558 reverse_rel = obj._all_columns.get(self._fields_id)
559 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
560 # if the o2m has a static domain we must respect it when unlinking
561 domain = self._domain(obj) if callable(self._domain) else self._domain
562 extra_domain = domain or []
563 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
564 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
565 # otherwise we only nullify the reverse foreign key column.
566 if reverse_rel.column.ondelete == "cascade":
567 obj.unlink(cr, user, ids_to_unlink, context=context)
569 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
571 # Must use write() to recompute parent_store structure if needed
572 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
574 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
575 ids3 = map(lambda x:x[0], cr.fetchall())
576 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
579 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
580 domain = self._domain(obj) if callable(self._domain) else self._domain
581 return obj.pool.get(self._obj).name_search(cr, uid, value, domain, operator, context=context,limit=limit)
585 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
586 raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)')
589 # Values: (0, 0, { fields }) create
590 # (1, ID, { fields }) update (write fields to ID)
591 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
592 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
593 # (4, ID) link (add a relationship)
595 # (6, ?, ids) set a list of links
597 class many2many(_column):
598 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
599 low-level details of the intermediary relationship table transparently.
600 A many-to-many relationship is always symmetrical, and can be declared and accessed
601 from either endpoint model.
602 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
603 or id2 (destination foreign key column name) are not specified, the system will
604 provide default values. This will by default only allow one single symmetrical
605 many-to-many relationship between the source and destination model.
606 For multiple many-to-many relationship between the same models and for
607 relationships where source and destination models are the same, ``rel``, ``id1``
608 and ``id2`` should be specified explicitly.
610 :param str obj: destination model
611 :param str rel: optional name of the intermediary relationship table. If not specified,
612 a canonical name will be derived based on the alphabetically-ordered
613 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
614 Automatic naming is not possible when the source and destination are
615 the same, for obvious ambiguity reasons.
616 :param str id1: optional name for the column holding the foreign key to the current
617 model in the relationship table. If not specified, a canonical name
618 will be derived based on the model name (in the form: `src_model_id`).
619 :param str id2: optional name for the column holding the foreign key to the destination
620 model in the relationship table. If not specified, a canonical name
621 will be derived based on the model name (in the form: `dest_model_id`)
622 :param str string: field label
624 _classic_read = False
625 _classic_write = False
629 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
632 _column.__init__(self, string=string, **args)
634 if rel and '.' in rel:
635 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
636 'You used %s, which is not a valid SQL table name.')% (string,rel))
642 def _sql_names(self, source_model):
643 """Return the SQL names defining the structure of the m2m relationship table
645 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
646 local_col is the name of the column holding the current model's FK, and
647 dest_col is the name of the column holding the destination model's FK, and
649 tbl, col1, col2 = self._rel, self._id1, self._id2
650 if not all((tbl, col1, col2)):
651 # the default table name is based on the stable alphabetical order of tables
652 dest_model = source_model.pool.get(self._obj)
653 tables = tuple(sorted([source_model._table, dest_model._table]))
655 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
656 'is not possible when source and destination models are '\
658 tbl = '%s_%s_rel' % tables
660 col1 = '%s_id' % source_model._table
662 col2 = '%s_id' % dest_model._table
663 return (tbl, col1, col2)
665 def _get_query_and_where_params(self, cr, model, ids, values, where_params):
666 """ Extracted from ``get`` to facilitate fine-tuning of the generated
668 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
669 FROM %(rel)s, %(from_c)s \
670 WHERE %(rel)s.%(id1)s IN %%s \
671 AND %(rel)s.%(id2)s = %(tbl)s.id \
677 return query, where_params
679 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
691 "Specifying offset at a many2many.get() is deprecated and may"
692 " produce unpredictable results.")
693 obj = model.pool.get(self._obj)
694 rel, id1, id2 = self._sql_names(model)
696 # static domains are lists, and are evaluated both here and on client-side, while string
697 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
698 # FIXME: make this distinction explicit in API!
699 domain = isinstance(self._domain, list) and self._domain or []
701 wquery = obj._where_calc(cr, user, domain, context=context)
702 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
703 from_c, where_c, where_params = wquery.get_sql()
705 where_c = ' AND ' + where_c
707 if offset or self._limit:
708 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
713 if self._limit is not None:
714 limit_str = ' LIMIT %d' % self._limit
716 query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel,
723 'order_by': order_by,
727 cr.execute(query, [tuple(ids),] + where_params)
728 for r in cr.fetchall():
729 res[r[1]].append(r[0])
732 def set(self, cr, model, id, name, values, user=None, context=None):
737 rel, id1, id2 = self._sql_names(model)
738 obj = model.pool.get(self._obj)
740 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
743 idnew = obj.create(cr, user, act[2], context=context)
744 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
746 obj.write(cr, user, [act[1]], act[2], context=context)
748 obj.unlink(cr, user, [act[1]], context=context)
750 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
752 # following queries are in the same transaction - so should be relatively safe
753 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
754 if not cr.fetchone():
755 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
757 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
760 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
762 d1 = ' and ' + ' and '.join(d1)
765 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)
767 for act_nbr in act[2]:
768 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
771 # TODO: use a name_search
773 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
774 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
777 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
778 raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)')
781 def get_nice_size(value):
783 if isinstance(value, (int,long)):
785 elif value: # this is supposed to be a string
787 return tools.human_size(size)
789 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
790 # and http://bugs.python.org/issue10066
791 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
793 def sanitize_binary_value(value):
794 # binary fields should be 7-bit ASCII base64-encoded data,
795 # but we do additional sanity checks to make sure the values
796 # are not something else that won't pass via XML-RPC
797 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
798 # these builtin types are meant to pass untouched
801 # Handle invalid bytes values that will cause problems
802 # for XML-RPC. See for more info:
803 # - http://bugs.python.org/issue10066
804 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
806 # Coercing to unicode would normally allow it to properly pass via
807 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
808 # (this works for _any_ byte values, thanks to the fallback
809 # to latin-1 passthrough encoding when decoding to unicode)
810 value = tools.ustr(value)
812 # Due to Python bug #10066 this could still yield invalid XML
813 # bytes, specifically in the low byte range, that will crash
814 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
815 # So check for low bytes values, and if any, perform
816 # base64 encoding - not very smart or useful, but this is
817 # our last resort to avoid crashing the request.
818 if invalid_xml_low_bytes.search(value):
819 # b64-encode after restoring the pure bytes with latin-1
820 # passthrough encoding
821 value = base64.b64encode(value.encode('latin-1'))
826 # ---------------------------------------------------------
828 # ---------------------------------------------------------
829 class function(_column):
831 A field whose value is computed by a function (rather
832 than being read from the database).
834 :param fnct: the callable that will compute the field value.
835 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
836 :param fnct_inv: the callable that will allow writing values in that field
837 (if not provided, the field is read-only).
838 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
840 :param str type: type of the field simulated by the function field
841 :param fnct_search: the callable that allows searching on the field
842 (if not provided, search will not return any result).
843 :param store: store computed value in database
844 (see :ref:`The *store* parameter <field-function-store>`).
845 :type store: True or dict specifying triggers for field computation
846 :param multi: name of batch for batch computation of function fields.
847 All fields with the same batch name will be computed by
848 a single function call. This changes the signature of the
851 .. _field-function-fnct: The ``fnct`` parameter
853 .. rubric:: The ``fnct`` parameter
855 The callable implementing the function field must have the following signature:
857 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
859 Implements the function field.
861 :param orm model: model to which the field belongs (should be ``self`` for
863 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
864 list of field names to compute.
865 :type field_name(s): str | [str]
866 :param arg: arbitrary value passed when declaring the function field
868 :return: mapping of ``ids`` to computed values, or if multi is provided,
869 to a map of field_names to computed values
871 The values in the returned dictionary must be of the type specified by the type
872 argument in the field declaration.
874 Here is an example with a simple function ``char`` function field::
877 def compute(self, cr, uid, ids, field_name, arg, context):
881 _columns['my_char'] = fields.function(compute, type='char', size=50)
883 # when called with ``ids=[1,2,3]``, ``compute`` could return:
887 3: False # null values should be returned explicitly too
890 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
891 of the field names that should be computed. Each value in the returned
892 dictionary must then be a dictionary mapping field names to values.
894 Here is an example where two function fields (``name`` and ``age``)
895 are both computed by a single function field::
898 def compute(self, cr, uid, ids, field_names, arg, context):
902 _columns['name'] = fields.function(compute_person_data, type='char',\
903 size=50, multi='person_data')
904 _columns[''age'] = fields.function(compute_person_data, type='integer',\
907 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
909 1: {'name': 'Bob', 'age': 23},
910 2: {'name': 'Sally', 'age': 19},
911 3: {'name': 'unknown', 'age': False}
914 .. _field-function-fnct-inv:
916 .. rubric:: The ``fnct_inv`` parameter
918 This callable implements the write operation for the function field
919 and must have the following signature:
921 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
923 Callable that implements the ``write`` operation for the function field.
925 :param orm model: model to which the field belongs (should be ``self`` for
927 :param int id: the identifier of the object to write on
928 :param str field_name: name of the field to set
929 :param fnct_inv_arg: arbitrary value passed when declaring the function field
932 When writing values for a function field, the ``multi`` parameter is ignored.
934 .. _field-function-fnct-search:
936 .. rubric:: The ``fnct_search`` parameter
938 This callable implements the search operation for the function field
939 and must have the following signature:
941 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
943 Callable that implements the ``search`` operation for the function field by expanding
944 a search criterion based on the function field into a new domain based only on
945 columns that are stored in the database.
947 :param orm model: model to which the field belongs (should be ``self`` for
949 :param orm model_again: same value as ``model`` (seriously! this is for backwards
951 :param str field_name: name of the field to search on
952 :param list criterion: domain component specifying the search criterion on the field.
954 :return: domain to use instead of ``criterion`` when performing the search.
955 This new domain must be based only on columns stored in the database, as it
956 will be used directly without any translation.
958 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
959 The most generic way to implement ``fnct_search`` is to directly search for the records that
960 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
961 ``[('id','in',[1,3,5])]``.
963 .. _field-function-store:
965 .. rubric:: The ``store`` parameter
967 The ``store`` parameter allows caching the result of the field computation in the
968 database, and defining the triggers that will invalidate that cache and force a
969 recomputation of the function field.
970 When not provided, the field is computed every time its value is read.
971 The value of ``store`` may be either ``True`` (to recompute the field value whenever
972 any field in the same record is modified), or a dictionary specifying a more
973 flexible set of recomputation triggers.
975 A trigger specification is a dictionary that maps the names of the models that
976 will trigger the computation, to a tuple describing the trigger rule, in the
980 'trigger_model': (mapping_function,
981 ['trigger_field1', 'trigger_field2'],
985 A trigger rule is defined by a 3-item tuple where:
987 * The ``mapping_function`` is defined as follows:
989 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
991 Callable that maps record ids of a trigger model to ids of the
992 corresponding records in the source model (whose field values
993 need to be recomputed).
995 :param orm model: trigger_model
996 :param list trigger_ids: ids of the records of trigger_model that were
999 :return: list of ids of the source model whose function field values
1000 need to be recomputed
1002 * The second item is a list of the fields who should act as triggers for
1003 the computation. If an empty list is given, all fields will act as triggers.
1004 * The last item is the priority, used to order the triggers when processing them
1005 after any write operation on a model that has function field triggers. The
1006 default priority is 10.
1008 In fact, setting store = True is the same as using the following trigger dict::
1011 'model_itself': (lambda self, cr, uid, ids, context: ids,
1017 _classic_read = False
1018 _classic_write = False
1024 # multi: compute several fields in one call
1026 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):
1027 _column.__init__(self, **args)
1030 self._fnct_inv = fnct_inv
1033 if 'relation' in args:
1034 self._obj = args['relation']
1036 self.digits = args.get('digits', (16,2))
1037 self.digits_compute = args.get('digits_compute', None)
1039 self._fnct_inv_arg = fnct_inv_arg
1043 self._fnct_search = fnct_search
1046 if not fnct_search and not store:
1047 self.selectable = False
1050 if self._type != 'many2one':
1051 # m2o fields need to return tuples with name_get, not just foreign keys
1052 self._classic_read = True
1053 self._classic_write = True
1055 self._symbol_get=lambda x:x and str(x)
1058 self._symbol_c = float._symbol_c
1059 self._symbol_f = float._symbol_f
1060 self._symbol_set = float._symbol_set
1062 if type == 'boolean':
1063 self._symbol_c = boolean._symbol_c
1064 self._symbol_f = boolean._symbol_f
1065 self._symbol_set = boolean._symbol_set
1067 if type == 'integer':
1068 self._symbol_c = integer._symbol_c
1069 self._symbol_f = integer._symbol_f
1070 self._symbol_set = integer._symbol_set
1072 def digits_change(self, cr):
1073 if self._type == 'float':
1074 if self.digits_compute:
1075 self.digits = self.digits_compute(cr)
1077 precision, scale = self.digits
1078 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1079 precision_digits=scale),
1080 precision_digits=scale))
1082 def search(self, cr, uid, obj, name, args, context=None):
1083 if not self._fnct_search:
1084 #CHECKME: should raise an exception
1086 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1088 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1092 field_type = obj._columns[field]._type
1093 if field_type == "many2one":
1094 # make the result a tuple if it is not already one
1095 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1096 obj_model = obj.pool.get(obj._columns[field].relation)
1097 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1098 result = (value, dict_names[value])
1100 if field_type == 'binary':
1101 if context.get('bin_size'):
1102 # client requests only the size of binary fields
1103 result = get_nice_size(value)
1104 elif not context.get('bin_raw'):
1105 result = sanitize_binary_value(value)
1107 if field_type == "integer" and value > xmlrpclib.MAXINT:
1108 # integer/long values greater than 2^31-1 are not supported
1109 # in pure XMLRPC, so we have to pass them as floats :-(
1110 # This is not needed for stored fields and non-functional integer
1111 # fields, as their values are constrained by the database backend
1112 # to the same 32bits signed int limit.
1113 result = __builtin__.float(value)
1116 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1117 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1119 if self._multi and id in result:
1120 for field, value in result[id].iteritems():
1122 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1123 elif result.get(id):
1124 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1127 def set(self, cr, obj, id, name, value, user=None, context=None):
1131 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1134 def _as_display_name(cls, field, cr, uid, obj, value, context=None):
1135 # Function fields are supposed to emulate a basic field type,
1136 # so they can delegate to the basic type for record name rendering
1137 return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
1139 # ---------------------------------------------------------
1141 # ---------------------------------------------------------
1143 class related(function):
1144 """Field that points to some data inside another field of the current record.
1149 'foo_id': fields.many2one('my.foo', 'Foo'),
1150 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1154 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1155 self._field_get2(cr, uid, obj, context)
1156 i = len(self._arg)-1
1159 if type(sarg) in [type([]), type( (1,) )]:
1160 where = [(self._arg[i], 'in', sarg)]
1162 where = [(self._arg[i], '=', sarg)]
1164 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1166 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1168 return [(self._arg[0], 'in', sarg)]
1170 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1171 self._field_get2(cr, uid, obj, context=context)
1172 if type(ids) != type([]):
1174 objlst = obj.browse(cr, uid, ids)
1178 for i in range(len(self.arg)):
1179 if not t_data: break
1180 field_detail = self._relations[i]
1181 if not t_data[self.arg[i]]:
1182 if self._type not in ('one2many', 'many2many'):
1185 elif field_detail['type'] in ('one2many', 'many2many'):
1186 if self._type != "many2one":
1188 t_data = t_data[self.arg[i]][0]
1193 t_data = t_data[self.arg[i]]
1195 model = obj.pool.get(self._relations[-1]['object'])
1196 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1198 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1199 self._field_get2(cr, uid, obj, context)
1200 if not ids: return {}
1201 relation = obj._name
1202 if self._type in ('one2many', 'many2many'):
1203 res = dict([(i, []) for i in ids])
1205 res = {}.fromkeys(ids, False)
1207 objlst = obj.browse(cr, SUPERUSER_ID, ids, context=context)
1212 relation = obj._name
1213 for i in range(len(self.arg)):
1214 field_detail = self._relations[i]
1215 relation = field_detail['object']
1217 if not t_data[self.arg[i]]:
1223 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1224 t_data = t_data[self.arg[i]][0]
1226 t_data = t_data[self.arg[i]]
1227 if type(t_data) == type(objlst[0]):
1228 res[data.id] = t_data.id
1230 res[data.id] = t_data
1231 if self._type=='many2one':
1232 ids = filter(None, res.values())
1234 # name_get as root, as seeing the name of a related
1235 # object depends on access right of source document,
1236 # not target, so user may not have access.
1237 ng = dict(obj.pool.get(self._obj).name_get(cr, SUPERUSER_ID, ids, context=context))
1240 res[r] = (res[r], ng[res[r]])
1241 elif self._type in ('one2many', 'many2many'):
1244 res[r] = [x.id for x in res[r]]
1247 def __init__(self, *arg, **args):
1249 self._relations = []
1250 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1251 if self.store is True:
1252 # TODO: improve here to change self.store = {...} according to related objects
1255 def _field_get2(self, cr, uid, obj, context=None):
1259 obj_name = obj._name
1260 for i in range(len(self._arg)):
1261 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1267 if f.get('relation',False):
1268 obj_name = f['relation']
1269 result[-1]['relation'] = f['relation']
1270 self._relations = result
1272 class sparse(function):
1274 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1276 + For a many2many field, a list of tuples is expected.
1277 Here is the list of tuple that are accepted, with the corresponding semantics ::
1279 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1280 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1281 (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)
1282 (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)
1283 (4, ID) link to existing record with id = ID (adds a relationship)
1284 (5) unlink all (like using (3,ID) for all linked records)
1285 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1288 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1290 + For a one2many field, a lits of tuples is expected.
1291 Here is the list of tuple that are accepted, with the corresponding semantics ::
1293 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1294 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1295 (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)
1298 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1301 if self._type == 'many2many':
1302 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1305 elif self._type == 'one2many':
1308 relation_obj = obj.pool.get(self.relation)
1310 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1312 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1314 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1316 relation_obj.unlink(cr, uid, vals[1], context=context)
1317 read_value.remove(vals[1])
1322 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1323 if not type(ids) == list:
1325 records = obj.browse(cr, uid, ids, context=context)
1326 for record in records:
1327 # grab serialized value as object - already deserialized
1328 serialized = getattr(record, self.serialization_field)
1330 # simply delete the key to unset it.
1331 serialized.pop(field_name, None)
1333 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1334 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1337 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1339 records = obj.browse(cr, uid, ids, context=context)
1340 for record in records:
1341 # grab serialized value as object - already deserialized
1342 serialized = getattr(record, self.serialization_field)
1343 results[record.id] = {}
1344 for field_name in field_names:
1345 field_type = obj._columns[field_name]._type
1346 value = serialized.get(field_name, False)
1347 if field_type in ('one2many','many2many'):
1350 # filter out deleted records as superuser
1351 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1352 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1353 if type(value) in (int,long) and field_type == 'many2one':
1354 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1355 # check for deleted record as superuser
1356 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1358 results[record.id][field_name] = value
1361 def __init__(self, serialization_field, **kwargs):
1362 self.serialization_field = serialization_field
1363 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1367 # ---------------------------------------------------------
1369 # ---------------------------------------------------------
1371 class dummy(function):
1372 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1375 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1378 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1381 def __init__(self, *arg, **args):
1383 self._relations = []
1384 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1386 # ---------------------------------------------------------
1388 # ---------------------------------------------------------
1390 class serialized(_column):
1391 """ A field able to store an arbitrary python data structure.
1393 Note: only plain components allowed.
1396 def _symbol_set_struct(val):
1397 return simplejson.dumps(val)
1399 def _symbol_get_struct(self, val):
1400 return simplejson.loads(val or '{}')
1403 _type = 'serialized'
1406 _symbol_f = _symbol_set_struct
1407 _symbol_set = (_symbol_c, _symbol_f)
1408 _symbol_get = _symbol_get_struct
1410 # TODO: review completly this class for speed improvement
1411 class property(function):
1413 def _get_default(self, obj, cr, uid, prop_name, context=None):
1414 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1416 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1417 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1419 :param list of string prop_names: list of name of property fields for those we want the default value
1420 :return: map of property field names to their default value
1423 prop = obj.pool.get('ir.property')
1425 for prop_name in prop_names:
1426 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1429 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1430 prop = obj.pool.get('ir.property')
1431 vids = [obj._name + ',' + str(oid) for oid in ids]
1433 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1434 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1436 domain = [('res_id', 'in', vids)] + domain
1437 return prop.search(cr, uid, domain, context=context)
1439 # TODO: to rewrite more clean
1440 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1444 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1446 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1448 default_val = self._get_default(obj, cr, uid, prop_name, context)
1450 property_create = False
1451 if isinstance(default_val, openerp.osv.orm.browse_record):
1452 if default_val.id != id_val:
1453 property_create = True
1454 elif default_val != id_val:
1455 property_create = True
1458 def_id = self._field_get(cr, uid, obj._name, prop_name)
1459 company = obj.pool.get('res.company')
1460 cid = company._company_default_get(cr, uid, obj._name, def_id,
1462 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1464 prop = obj.pool.get('ir.property')
1465 return prop.create(cr, uid, {
1466 'name': propdef.name,
1468 'res_id': obj._name+','+str(id),
1470 'fields_id': def_id,
1475 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1476 prop = obj.pool.get('ir.property')
1477 # get the default values (for res_id = False) for the property fields
1478 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1480 # build the dictionary that will be returned
1483 res[id] = default_val.copy()
1485 for prop_name in prop_names:
1486 property_field = obj._all_columns.get(prop_name).column
1487 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1488 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1489 # in order to make a name_get in batch for all the ids needed.
1492 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1493 obj_reference = obj._name + ',' + str(id)
1494 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1496 res[id][prop_name] = value
1497 # Check existence as root (as seeing the name of a related
1498 # object depends on access right of source document,
1499 # not target, so user may not have access) in order to avoid
1500 # pointing on an unexisting record.
1501 if property_destination_obj:
1502 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, SUPERUSER_ID, res[id][prop_name].id):
1503 name_get_ids[id] = res[id][prop_name].id
1505 res[id][prop_name] = False
1506 if property_destination_obj:
1507 # name_get as root (as seeing the name of a related
1508 # object depends on access right of source document,
1509 # not target, so user may not have access.)
1510 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, SUPERUSER_ID, name_get_ids.values(), context=context))
1511 # the property field is a m2o, we need to return a tuple with (id, name)
1512 for k, v in name_get_ids.iteritems():
1513 if res[k][prop_name]:
1514 res[k][prop_name] = (v , name_get_values.get(v))
1517 def _field_get(self, cr, uid, model_name, prop):
1518 if not self.field_id.get(cr.dbname):
1519 cr.execute('SELECT id \
1520 FROM ir_model_fields \
1521 WHERE name=%s AND model=%s', (prop, model_name))
1523 self.field_id[cr.dbname] = res and res[0]
1524 return self.field_id[cr.dbname]
1526 def __init__(self, obj_prop, **args):
1527 # TODO remove obj_prop parameter (use many2one type)
1529 function.__init__(self, self._fnct_read, False, self._fnct_write,
1530 obj_prop, multi='properties', **args)
1536 def field_to_dict(model, cr, user, field, context=None):
1537 """ Return a dictionary representation of a field.
1539 The string, help, and selection attributes (if any) are untranslated. This
1540 representation is the one returned by fields_get() (fields_get() will do
1545 res = {'type': field._type}
1546 # some attributes for m2m/function field are added as debug info only
1547 if isinstance(field, function):
1548 res['function'] = field._fnct and field._fnct.func_name or False
1549 res['store'] = field.store
1550 if isinstance(field.store, dict):
1551 res['store'] = str(field.store)
1552 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1553 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1554 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1555 res['func_obj'] = field._obj or False
1556 if isinstance(field, many2many):
1557 (table, col1, col2) = field._sql_names(model)
1558 res['related_columns'] = [col1, col2]
1559 res['third_table'] = table
1560 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1561 'change_default', 'translate', 'help', 'select', 'selectable', 'groups'):
1562 if getattr(field, arg):
1563 res[arg] = getattr(field, arg)
1564 for arg in ('digits', 'invisible', 'filters'):
1565 if getattr(field, arg, None):
1566 res[arg] = getattr(field, arg)
1569 res['string'] = field.string
1571 res['help'] = field.help
1573 if hasattr(field, 'selection'):
1574 if isinstance(field.selection, (tuple, list)):
1575 res['selection'] = field.selection
1577 # call the 'dynamic selection' function
1578 res['selection'] = field.selection(model, cr, user, context)
1579 if res['type'] in ('one2many', 'many2many', 'many2one'):
1580 res['relation'] = field._obj
1581 res['domain'] = field._domain(model) if callable(field._domain) else field._domain
1582 res['context'] = field._context
1584 if isinstance(field, one2many):
1585 res['relation_field'] = field._fields_id
1590 class column_info(object):
1591 """ Struct containing details about an osv column, either one local to
1592 its model, or one inherited via _inherits.
1598 .. attribute:: column
1600 column instance, subclass of :class:`_column`
1602 .. attribute:: parent_model
1604 if the column is inherited, name of the model that contains it,
1605 ``None`` for local columns.
1607 .. attribute:: parent_column
1609 the name of the column containing the m2o relationship to the
1610 parent model that contains this column, ``None`` for local columns.
1612 .. attribute:: original_parent
1614 if the column is inherited, name of the original parent model that
1615 contains it i.e in case of multilevel inheritance, ``None`` for
1618 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1620 self.column = column
1621 self.parent_model = parent_model
1622 self.parent_column = parent_column
1623 self.original_parent = original_parent
1626 return '%s(%s, %s, %s, %s, %s)' % (
1627 self.__name__, self.name, self.column,
1628 self.parent_model, self.parent_column, self.original_parent)
1630 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: