1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 - relations (one2many, many2one, many2many)
28 * _classic_read: is a classic sql fields
41 from psycopg2 import Binary
44 import openerp.tools as tools
45 from openerp.tools.translate import _
46 from openerp.tools import float_round, float_repr
49 _logger = logging.getLogger(__name__)
51 def _symbol_set(symb):
52 if symb == None or symb == False:
54 elif isinstance(symb, unicode):
55 return symb.encode('utf-8')
59 class _column(object):
60 """ Base of all fields, a database column
62 An instance of this object is a *description* of a database column. It will
63 not hold any data, but only provide the methods to manipulate data of an
64 ORM record or even prepare/update the database to hold such a field of data.
74 _symbol_f = _symbol_set
75 _symbol_set = (_symbol_c, _symbol_f)
78 # used to hide a certain field type in the list of field types
81 def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
84 The 'manual' keyword argument specifies if the field is a custom one.
85 It corresponds to the 'state' column in ir_model_fields.
92 self.states = states or {}
94 self.readonly = readonly
95 self.required = required
97 self.help = args.get('help', '')
98 self.priority = priority
99 self.change_default = change_default
100 self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
101 self.translate = translate
102 self._domain = domain
103 self._context = context
109 self.selectable = True
110 self.group_operator = args.get('group_operator', False)
113 setattr(self, a, args[a])
118 def set(self, cr, obj, id, name, value, user=None, context=None):
119 cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
121 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
122 raise Exception(_('undefined get method !'))
124 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
125 ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
126 res = obj.read(cr, uid, ids, [name], context=context)
127 return [x[name] for x in res]
130 # ---------------------------------------------------------
132 # ---------------------------------------------------------
133 class boolean(_column):
136 _symbol_f = lambda x: x and 'True' or 'False'
137 _symbol_set = (_symbol_c, _symbol_f)
139 def __init__(self, string='unknown', required=False, **args):
140 super(boolean, self).__init__(string=string, required=required, **args)
143 "required=True is deprecated: making a boolean field"
144 " `required` has no effect, as NULL values are "
145 "automatically turned into False.")
147 class integer(_column):
150 _symbol_f = lambda x: int(x or 0)
151 _symbol_set = (_symbol_c, _symbol_f)
152 _symbol_get = lambda self,x: x or 0
154 def __init__(self, string='unknown', required=False, **args):
155 super(integer, self).__init__(string=string, required=required, **args)
158 "required=True is deprecated: making an integer field"
159 " `required` has no effect, as NULL values are "
160 "automatically turned into 0.")
162 class integer_big(_column):
163 """Experimental 64 bit integer column type, currently unused.
165 TODO: this field should work fine for values up
166 to 32 bits, but greater values will not fit
167 in the XML-RPC int type, so a specific
168 get() method is needed to pass them as floats,
169 like what we do for integer functional fields.
171 _type = 'integer_big'
172 # do not reference the _symbol_* of integer class, as that would possibly
173 # unbind the lambda functions
175 _symbol_f = lambda x: int(x or 0)
176 _symbol_set = (_symbol_c, _symbol_f)
177 _symbol_get = lambda self,x: x or 0
180 def __init__(self, string='unknown', required=False, **args):
181 super(integer_big, self).__init__(string=string, required=required, **args)
184 "required=True is deprecated: making an integer_big field"
185 " `required` has no effect, as NULL values are "
186 "automatically turned into 0.")
188 class reference(_column):
190 _classic_read = False # post-process to handle missing target
192 def __init__(self, string, selection, size, **args):
193 _column.__init__(self, string=string, size=size, selection=selection, **args)
195 def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
197 # copy initial values fetched previously.
199 result[value['id']] = value[name]
201 model, res_id = value[name].split(',')
202 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
203 result[value['id']] = False
209 def __init__(self, string, size, **args):
210 _column.__init__(self, string=string, size=size, **args)
211 self._symbol_set = (self._symbol_c, self._symbol_set_char)
213 # takes a string (encoded in utf8) and returns a string (encoded in utf8)
214 def _symbol_set_char(self, symb):
216 # * we need to remove the "symb==False" from the next line BUT
217 # for now too many things rely on this broken behavior
218 # * the symb==None test should be common to all data types
219 if symb == None or symb == False:
222 # we need to convert the string to a unicode object to be able
223 # to evaluate its length (and possibly truncate it) reliably
224 u_symb = tools.ustr(symb)
226 return u_symb[:self.size].encode('utf8')
234 class float(_column):
237 _symbol_f = lambda x: __builtin__.float(x or 0.0)
238 _symbol_set = (_symbol_c, _symbol_f)
239 _symbol_get = lambda self,x: x or 0.0
241 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
242 _column.__init__(self, string=string, required=required, **args)
244 # synopsis: digits_compute(cr) -> (precision, scale)
245 self.digits_compute = digits_compute
248 "required=True is deprecated: making a float field"
249 " `required` has no effect, as NULL values are "
250 "automatically turned into 0.0.")
252 def digits_change(self, cr):
253 if self.digits_compute:
254 self.digits = self.digits_compute(cr)
256 precision, scale = self.digits
257 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
258 precision_digits=scale),
259 precision_digits=scale))
266 """ Returns the current date in a format fit for being a
267 default value to a ``date`` field.
269 This method should be provided as is to the _defaults dict, it
270 should not be called.
272 return DT.date.today().strftime(
273 tools.DEFAULT_SERVER_DATE_FORMAT)
276 def context_today(model, cr, uid, context=None, timestamp=None):
277 """Returns the current date as seen in the client's timezone
278 in a format fit for date fields.
279 This method may be passed as value to initialize _defaults.
281 :param Model model: model (osv) for which the date value is being
282 computed - technical field, currently ignored,
283 automatically passed when used in _defaults.
284 :param datetime timestamp: optional datetime value to use instead of
285 the current date and time (must be a
286 datetime, regular dates can't be converted
288 :param dict context: the 'tz' key in the context should give the
289 name of the User/Client timezone (otherwise
293 today = timestamp or DT.datetime.now()
295 if context and context.get('tz'):
297 utc = pytz.timezone('UTC')
298 context_tz = pytz.timezone(context['tz'])
299 utc_today = utc.localize(today, is_dst=False) # UTC = no DST
300 context_today = utc_today.astimezone(context_tz)
302 _logger.debug("failed to compute context/client-specific today date, "
303 "using the UTC value for `today`",
305 return (context_today or today).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
307 class datetime(_column):
311 """ Returns the current datetime in a format fit for being a
312 default value to a ``datetime`` field.
314 This method should be provided as is to the _defaults dict, it
315 should not be called.
317 return DT.datetime.now().strftime(
318 tools.DEFAULT_SERVER_DATETIME_FORMAT)
321 def context_timestamp(cr, uid, timestamp, context=None):
322 """Returns the given timestamp converted to the client's timezone.
323 This method is *not* meant for use as a _defaults initializer,
324 because datetime fields are automatically converted upon
325 display on client side. For _defaults you :meth:`fields.datetime.now`
326 should be used instead.
328 :param datetime timestamp: naive datetime value (expressed in UTC)
329 to be converted to the client timezone
330 :param dict context: the 'tz' key in the context should give the
331 name of the User/Client timezone (otherwise
334 :return: timestamp converted to timezone-aware datetime in context
337 assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
338 if context and context.get('tz'):
340 utc = pytz.timezone('UTC')
341 context_tz = pytz.timezone(context['tz'])
342 utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
343 return utc_timestamp.astimezone(context_tz)
345 _logger.debug("failed to compute context/client-specific timestamp, "
346 "using the UTC value",
355 """ Returns the current time in a format fit for being a
356 default value to a ``time`` field.
358 This method should be proivided as is to the _defaults dict,
359 it should not be called.
361 return DT.datetime.now().strftime(
362 tools.DEFAULT_SERVER_TIME_FORMAT)
364 class binary(_column):
368 # Binary values may be byte strings (python 2.6 byte array), but
369 # the legacy OpenERP convention is to transfer and store binaries
370 # as base64-encoded strings. The base64 string may be provided as a
371 # unicode in some circumstances, hence the str() cast in symbol_f.
372 # This str coercion will only work for pure ASCII unicode strings,
373 # on purpose - non base64 data must be passed as a 8bit byte strings.
374 _symbol_f = lambda symb: symb and Binary(str(symb)) or None
376 _symbol_set = (_symbol_c, _symbol_f)
377 _symbol_get = lambda self, x: x and str(x)
379 _classic_read = False
382 def __init__(self, string='unknown', filters=None, **args):
383 _column.__init__(self, string=string, **args)
384 self.filters = filters
386 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
399 # If client is requesting only the size of the field, we return it instead
400 # of the content. Presumably a separate request will be done to read the actual
401 # content if it's needed at some point.
402 # TODO: after 6.0 we should consider returning a dict with size and content instead of
403 # having an implicit convention for the value
404 if val and context.get('bin_size_%s' % name, context.get('bin_size')):
405 res[i] = tools.human_size(long(val))
410 class selection(_column):
413 def __init__(self, selection, string='unknown', **args):
414 _column.__init__(self, string=string, **args)
415 self.selection = selection
417 # ---------------------------------------------------------
419 # ---------------------------------------------------------
422 # Values: (0, 0, { fields }) create
423 # (1, ID, { fields }) update
424 # (2, ID) remove (delete)
425 # (3, ID) unlink one (target id or target of relation)
427 # (5) unlink all (only valid for one2many)
429 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
430 class one2one(_column):
431 _classic_read = False
432 _classic_write = True
436 def __init__(self, obj, string='unknown', **args):
437 _logger.warning("The one2one field is deprecated and doesn't work anymore.")
438 _column.__init__(self, string=string, **args)
441 def set(self, cr, obj_src, id, field, act, user=None, context=None):
444 obj = obj_src.pool.get(self._obj)
445 self._table = obj_src.pool.get(self._obj)._table
447 id_new = obj.create(cr, user, act[1])
448 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
450 cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
451 id = cr.fetchone()[0]
452 obj.write(cr, user, [id], act[1], context=context)
454 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
455 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
458 class many2one(_column):
459 _classic_read = False
460 _classic_write = True
463 _symbol_f = lambda x: x or None
464 _symbol_set = (_symbol_c, _symbol_f)
466 def __init__(self, obj, string='unknown', **args):
467 _column.__init__(self, string=string, **args)
470 def get(self, cr, obj, ids, name, user=None, context=None, values=None):
478 res[r['id']] = r[name]
480 res.setdefault(id, '')
481 obj = obj.pool.get(self._obj)
483 # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
484 # we use uid=1 because the visibility of a many2one field value (just id and name)
485 # must be the access right of the parent form and not the linked object itself.
486 records = dict(obj.name_get(cr, 1,
487 list(set([x for x in res.values() if isinstance(x, (int,long))])),
490 if res[id] in records:
491 res[id] = (res[id], records[res[id]])
496 def set(self, cr, obj_src, id, field, values, user=None, context=None):
499 obj = obj_src.pool.get(self._obj)
500 self._table = obj_src.pool.get(self._obj)._table
501 if type(values) == type([]):
504 id_new = obj.create(cr, act[2])
505 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
507 obj.write(cr, [act[1]], act[2], context=context)
509 cr.execute('delete from '+self._table+' where id=%s', (act[1],))
510 elif act[0] == 3 or act[0] == 5:
511 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
513 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
516 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
518 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
520 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
521 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
524 class one2many(_column):
525 _classic_read = False
526 _classic_write = False
530 def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
531 _column.__init__(self, string=string, **args)
533 self._fields_id = fields_id
535 #one2many can't be used as condition for defaults
536 assert(self.change_default != True)
538 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
542 context = context.copy()
543 context.update(self._context)
551 ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
552 for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
553 if r[self._fields_id] in res:
554 res[r[self._fields_id]].append(r['id'])
557 def set(self, cr, obj, id, field, values, user=None, context=None):
562 context = context.copy()
563 context.update(self._context)
564 context['no_store_function'] = True
567 _table = obj.pool.get(self._obj)._table
568 obj = obj.pool.get(self._obj)
571 act[2][self._fields_id] = id
572 id_new = obj.create(cr, user, act[2], context=context)
573 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
575 obj.write(cr, user, [act[1]], act[2], context=context)
577 obj.unlink(cr, user, [act[1]], context=context)
579 reverse_rel = obj._all_columns.get(self._fields_id)
580 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
581 # if the model has on delete cascade, just delete the row
582 if reverse_rel.column.ondelete == "cascade":
583 obj.unlink(cr, user, [act[1]], context=context)
585 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
587 # Must use write() to recompute parent_store structure if needed
588 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
590 reverse_rel = obj._all_columns.get(self._fields_id)
591 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
592 # if the o2m has a static domain we must respect it when unlinking
593 extra_domain = self._domain if isinstance(getattr(self, '_domain', None), list) else []
594 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
595 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
596 # otherwise we only nullify the reverse foreign key column.
597 if reverse_rel.column.ondelete == "cascade":
598 obj.unlink(cr, user, ids_to_unlink, context=context)
600 obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
602 # Must use write() to recompute parent_store structure if needed
603 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
605 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
606 ids3 = map(lambda x:x[0], cr.fetchall())
607 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
610 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
611 return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
615 # Values: (0, 0, { fields }) create
616 # (1, ID, { fields }) update (write fields to ID)
617 # (2, ID) remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
618 # (3, ID) unlink (delete the relationship between the two objects but does not delete ID)
619 # (4, ID) link (add a relationship)
621 # (6, ?, ids) set a list of links
623 class many2many(_column):
624 """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
625 low-level details of the intermediary relationship table transparently.
626 A many-to-many relationship is always symmetrical, and can be declared and accessed
627 from either endpoint model.
628 If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
629 or id2 (destination foreign key column name) are not specified, the system will
630 provide default values. This will by default only allow one single symmetrical
631 many-to-many relationship between the source and destination model.
632 For multiple many-to-many relationship between the same models and for
633 relationships where source and destination models are the same, ``rel``, ``id1``
634 and ``id2`` should be specified explicitly.
636 :param str obj: destination model
637 :param str rel: optional name of the intermediary relationship table. If not specified,
638 a canonical name will be derived based on the alphabetically-ordered
639 model names of the source and destination (in the form: ``amodel_bmodel_rel``).
640 Automatic naming is not possible when the source and destination are
641 the same, for obvious ambiguity reasons.
642 :param str id1: optional name for the column holding the foreign key to the current
643 model in the relationship table. If not specified, a canonical name
644 will be derived based on the model name (in the form: `src_model_id`).
645 :param str id2: optional name for the column holding the foreign key to the destination
646 model in the relationship table. If not specified, a canonical name
647 will be derived based on the model name (in the form: `dest_model_id`)
648 :param str string: field label
650 _classic_read = False
651 _classic_write = False
655 def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
658 _column.__init__(self, string=string, **args)
660 if rel and '.' in rel:
661 raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
662 'You used %s, which is not a valid SQL table name.')% (string,rel))
668 def _sql_names(self, source_model):
669 """Return the SQL names defining the structure of the m2m relationship table
671 :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
672 local_col is the name of the column holding the current model's FK, and
673 dest_col is the name of the column holding the destination model's FK, and
675 tbl, col1, col2 = self._rel, self._id1, self._id2
676 if not all((tbl, col1, col2)):
677 # the default table name is based on the stable alphabetical order of tables
678 dest_model = source_model.pool.get(self._obj)
679 tables = tuple(sorted([source_model._table, dest_model._table]))
681 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
682 'is not possible when source and destination models are '\
684 tbl = '%s_%s_rel' % tables
686 col1 = '%s_id' % source_model._table
688 col2 = '%s_id' % dest_model._table
689 return (tbl, col1, col2)
691 def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
703 "Specifying offset at a many2many.get() is deprecated and may"
704 " produce unpredictable results.")
705 obj = model.pool.get(self._obj)
706 rel, id1, id2 = self._sql_names(model)
708 # static domains are lists, and are evaluated both here and on client-side, while string
709 # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
710 # FIXME: make this distinction explicit in API!
711 domain = isinstance(self._domain, list) and self._domain or []
713 wquery = obj._where_calc(cr, user, domain, context=context)
714 obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
715 from_c, where_c, where_params = wquery.get_sql()
717 where_c = ' AND ' + where_c
719 order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
722 if self._limit is not None:
723 limit_str = ' LIMIT %d' % self._limit
725 query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
726 FROM %(rel)s, %(from_c)s \
727 WHERE %(rel)s.%(id1)s IN %%s \
728 AND %(rel)s.%(id2)s = %(tbl)s.id \
740 'order_by': order_by,
743 cr.execute(query, [tuple(ids),] + where_params)
744 for r in cr.fetchall():
745 res[r[1]].append(r[0])
748 def set(self, cr, model, id, name, values, user=None, context=None):
753 rel, id1, id2 = self._sql_names(model)
754 obj = model.pool.get(self._obj)
756 if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
759 idnew = obj.create(cr, user, act[2], context=context)
760 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
762 obj.write(cr, user, [act[1]], act[2], context=context)
764 obj.unlink(cr, user, [act[1]], context=context)
766 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
768 # following queries are in the same transaction - so should be relatively safe
769 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
770 if not cr.fetchone():
771 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
773 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
776 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
778 d1 = ' and ' + ' and '.join(d1)
781 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)
783 for act_nbr in act[2]:
784 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
787 # TODO: use a name_search
789 def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
790 return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
793 def get_nice_size(value):
795 if isinstance(value, (int,long)):
797 elif value: # this is supposed to be a string
799 return tools.human_size(size)
801 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
802 # and http://bugs.python.org/issue10066
803 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
805 def sanitize_binary_value(value):
806 # binary fields should be 7-bit ASCII base64-encoded data,
807 # but we do additional sanity checks to make sure the values
808 # are not something else that won't pass via XML-RPC
809 if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
810 # these builtin types are meant to pass untouched
813 # Handle invalid bytes values that will cause problems
814 # for XML-RPC. See for more info:
815 # - http://bugs.python.org/issue10066
816 # - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
818 # Coercing to unicode would normally allow it to properly pass via
819 # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
820 # (this works for _any_ byte values, thanks to the fallback
821 # to latin-1 passthrough encoding when decoding to unicode)
822 value = tools.ustr(value)
824 # Due to Python bug #10066 this could still yield invalid XML
825 # bytes, specifically in the low byte range, that will crash
826 # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
827 # So check for low bytes values, and if any, perform
828 # base64 encoding - not very smart or useful, but this is
829 # our last resort to avoid crashing the request.
830 if invalid_xml_low_bytes.search(value):
831 # b64-encode after restoring the pure bytes with latin-1
832 # passthrough encoding
833 value = base64.b64encode(value.encode('latin-1'))
838 # ---------------------------------------------------------
840 # ---------------------------------------------------------
841 class function(_column):
843 A field whose value is computed by a function (rather
844 than being read from the database).
846 :param fnct: the callable that will compute the field value.
847 :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
848 :param fnct_inv: the callable that will allow writing values in that field
849 (if not provided, the field is read-only).
850 :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
852 :param str type: type of the field simulated by the function field
853 :param fnct_search: the callable that allows searching on the field
854 (if not provided, search will not return any result).
855 :param store: store computed value in database
856 (see :ref:`The *store* parameter <field-function-store>`).
857 :type store: True or dict specifying triggers for field computation
858 :param multi: name of batch for batch computation of function fields.
859 All fields with the same batch name will be computed by
860 a single function call. This changes the signature of the
863 .. _field-function-fnct: The ``fnct`` parameter
865 .. rubric:: The ``fnct`` parameter
867 The callable implementing the function field must have the following signature:
869 .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
871 Implements the function field.
873 :param orm model: model to which the field belongs (should be ``self`` for
875 :param field_name(s): name of the field to compute, or if ``multi`` is provided,
876 list of field names to compute.
877 :type field_name(s): str | [str]
878 :param arg: arbitrary value passed when declaring the function field
880 :return: mapping of ``ids`` to computed values, or if multi is provided,
881 to a map of field_names to computed values
883 The values in the returned dictionary must be of the type specified by the type
884 argument in the field declaration.
886 Here is an example with a simple function ``char`` function field::
889 def compute(self, cr, uid, ids, field_name, arg, context):
893 _columns['my_char'] = fields.function(compute, type='char', size=50)
895 # when called with ``ids=[1,2,3]``, ``compute`` could return:
899 3: False # null values should be returned explicitly too
902 If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
903 of the field names that should be computed. Each value in the returned
904 dictionary must then be a dictionary mapping field names to values.
906 Here is an example where two function fields (``name`` and ``age``)
907 are both computed by a single function field::
910 def compute(self, cr, uid, ids, field_names, arg, context):
914 _columns['name'] = fields.function(compute_person_data, type='char',\
915 size=50, multi='person_data')
916 _columns[''age'] = fields.function(compute_person_data, type='integer',\
919 # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
921 1: {'name': 'Bob', 'age': 23},
922 2: {'name': 'Sally', 'age': 19},
923 3: {'name': 'unknown', 'age': False}
926 .. _field-function-fnct-inv:
928 .. rubric:: The ``fnct_inv`` parameter
930 This callable implements the write operation for the function field
931 and must have the following signature:
933 .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
935 Callable that implements the ``write`` operation for the function field.
937 :param orm model: model to which the field belongs (should be ``self`` for
939 :param int id: the identifier of the object to write on
940 :param str field_name: name of the field to set
941 :param fnct_inv_arg: arbitrary value passed when declaring the function field
944 When writing values for a function field, the ``multi`` parameter is ignored.
946 .. _field-function-fnct-search:
948 .. rubric:: The ``fnct_search`` parameter
950 This callable implements the search operation for the function field
951 and must have the following signature:
953 .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
955 Callable that implements the ``search`` operation for the function field by expanding
956 a search criterion based on the function field into a new domain based only on
957 columns that are stored in the database.
959 :param orm model: model to which the field belongs (should be ``self`` for
961 :param orm model_again: same value as ``model`` (seriously! this is for backwards
963 :param str field_name: name of the field to search on
964 :param list criterion: domain component specifying the search criterion on the field.
966 :return: domain to use instead of ``criterion`` when performing the search.
967 This new domain must be based only on columns stored in the database, as it
968 will be used directly without any translation.
970 The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
971 The most generic way to implement ``fnct_search`` is to directly search for the records that
972 match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
973 ``[('id','in',[1,3,5])]``.
975 .. _field-function-store:
977 .. rubric:: The ``store`` parameter
979 The ``store`` parameter allows caching the result of the field computation in the
980 database, and defining the triggers that will invalidate that cache and force a
981 recomputation of the function field.
982 When not provided, the field is computed every time its value is read.
983 The value of ``store`` may be either ``True`` (to recompute the field value whenever
984 any field in the same record is modified), or a dictionary specifying a more
985 flexible set of recomputation triggers.
987 A trigger specification is a dictionary that maps the names of the models that
988 will trigger the computation, to a tuple describing the trigger rule, in the
992 'trigger_model': (mapping_function,
993 ['trigger_field1', 'trigger_field2'],
997 A trigger rule is defined by a 3-item tuple where:
999 * The ``mapping_function`` is defined as follows:
1001 .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
1003 Callable that maps record ids of a trigger model to ids of the
1004 corresponding records in the source model (whose field values
1005 need to be recomputed).
1007 :param orm model: trigger_model
1008 :param list trigger_ids: ids of the records of trigger_model that were
1011 :return: list of ids of the source model whose function field values
1012 need to be recomputed
1014 * The second item is a list of the fields who should act as triggers for
1015 the computation. If an empty list is given, all fields will act as triggers.
1016 * The last item is the priority, used to order the triggers when processing them
1017 after any write operation on a model that has function field triggers. The
1018 default priority is 10.
1020 In fact, setting store = True is the same as using the following trigger dict::
1023 'model_itself': (lambda self, cr, uid, ids, context: ids,
1029 _classic_read = False
1030 _classic_write = False
1036 # multi: compute several fields in one call
1038 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):
1039 _column.__init__(self, **args)
1042 self._fnct_inv = fnct_inv
1045 if 'relation' in args:
1046 self._obj = args['relation']
1048 self.digits = args.get('digits', (16,2))
1049 self.digits_compute = args.get('digits_compute', None)
1051 self._fnct_inv_arg = fnct_inv_arg
1055 self._fnct_search = fnct_search
1058 if not fnct_search and not store:
1059 self.selectable = False
1062 if self._type != 'many2one':
1063 # m2o fields need to return tuples with name_get, not just foreign keys
1064 self._classic_read = True
1065 self._classic_write = True
1067 self._symbol_get=lambda x:x and str(x)
1069 self._prefetch = True
1072 self._symbol_c = float._symbol_c
1073 self._symbol_f = float._symbol_f
1074 self._symbol_set = float._symbol_set
1076 if type == 'boolean':
1077 self._symbol_c = boolean._symbol_c
1078 self._symbol_f = boolean._symbol_f
1079 self._symbol_set = boolean._symbol_set
1081 if type in ['integer','integer_big']:
1082 self._symbol_c = integer._symbol_c
1083 self._symbol_f = integer._symbol_f
1084 self._symbol_set = integer._symbol_set
1086 def digits_change(self, cr):
1087 if self._type == 'float':
1088 if self.digits_compute:
1089 self.digits = self.digits_compute(cr)
1091 precision, scale = self.digits
1092 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1093 precision_digits=scale),
1094 precision_digits=scale))
1096 def search(self, cr, uid, obj, name, args, context=None):
1097 if not self._fnct_search:
1098 #CHECKME: should raise an exception
1100 return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1102 def postprocess(self, cr, uid, obj, field, value=None, context=None):
1106 field_type = obj._columns[field]._type
1107 if field_type == "many2one":
1108 # make the result a tuple if it is not already one
1109 if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1110 obj_model = obj.pool.get(obj._columns[field].relation)
1111 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1112 result = (value, dict_names[value])
1114 if field_type == 'binary':
1115 if context.get('bin_size'):
1116 # client requests only the size of binary fields
1117 result = get_nice_size(value)
1118 elif not context.get('bin_raw'):
1119 result = sanitize_binary_value(value)
1121 if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1122 # integer/long values greater than 2^31-1 are not supported
1123 # in pure XMLRPC, so we have to pass them as floats :-(
1124 # This is not needed for stored fields and non-functional integer
1125 # fields, as their values are constrained by the database backend
1126 # to the same 32bits signed int limit.
1127 result = float(value)
1130 def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1131 result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1133 if self._multi and id in result:
1134 for field, value in result[id].iteritems():
1136 result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1137 elif result.get(id):
1138 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1141 def set(self, cr, obj, id, name, value, user=None, context=None):
1145 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1147 # ---------------------------------------------------------
1149 # ---------------------------------------------------------
1151 class related(function):
1152 """Field that points to some data inside another field of the current record.
1157 'foo_id': fields.many2one('my.foo', 'Foo'),
1158 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1162 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1163 self._field_get2(cr, uid, obj, context)
1164 i = len(self._arg)-1
1167 if type(sarg) in [type([]), type( (1,) )]:
1168 where = [(self._arg[i], 'in', sarg)]
1170 where = [(self._arg[i], '=', sarg)]
1172 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1174 sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1176 return [(self._arg[0], 'in', sarg)]
1178 def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1179 self._field_get2(cr, uid, obj, context=context)
1180 if type(ids) != type([]):
1182 objlst = obj.browse(cr, uid, ids)
1186 for i in range(len(self.arg)):
1187 if not t_data: break
1188 field_detail = self._relations[i]
1189 if not t_data[self.arg[i]]:
1190 if self._type not in ('one2many', 'many2many'):
1193 elif field_detail['type'] in ('one2many', 'many2many'):
1194 if self._type != "many2one":
1196 t_data = t_data[self.arg[i]][0]
1201 t_data = t_data[self.arg[i]]
1203 model = obj.pool.get(self._relations[-1]['object'])
1204 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1206 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1207 self._field_get2(cr, uid, obj, context)
1208 if not ids: return {}
1209 relation = obj._name
1210 if self._type in ('one2many', 'many2many'):
1211 res = dict([(i, []) for i in ids])
1213 res = {}.fromkeys(ids, False)
1215 objlst = obj.browse(cr, 1, ids, context=context)
1220 relation = obj._name
1221 for i in range(len(self.arg)):
1222 field_detail = self._relations[i]
1223 relation = field_detail['object']
1225 if not t_data[self.arg[i]]:
1231 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1232 t_data = t_data[self.arg[i]][0]
1234 t_data = t_data[self.arg[i]]
1235 if type(t_data) == type(objlst[0]):
1236 res[data.id] = t_data.id
1238 res[data.id] = t_data
1239 if self._type=='many2one':
1240 ids = filter(None, res.values())
1242 # name_get as root, as seeing the name of a related
1243 # object depends on access right of source document,
1244 # not target, so user may not have access.
1245 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1248 res[r] = (res[r], ng[res[r]])
1249 elif self._type in ('one2many', 'many2many'):
1252 res[r] = [x.id for x in res[r]]
1255 def __init__(self, *arg, **args):
1257 self._relations = []
1258 super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1259 if self.store is True:
1260 # TODO: improve here to change self.store = {...} according to related objects
1263 def _field_get2(self, cr, uid, obj, context=None):
1267 obj_name = obj._name
1268 for i in range(len(self._arg)):
1269 f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1275 if f.get('relation',False):
1276 obj_name = f['relation']
1277 result[-1]['relation'] = f['relation']
1278 self._relations = result
1281 class sparse(function):
1283 def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
1285 + For a many2many field, a list 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)
1291 (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)
1292 (4, ID) link to existing record with id = ID (adds a relationship)
1293 (5) unlink all (like using (3,ID) for all linked records)
1294 (6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1297 [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1299 + For a one2many field, a lits of tuples is expected.
1300 Here is the list of tuple that are accepted, with the corresponding semantics ::
1302 (0, 0, { values }) link to a new record that needs to be created with the given values dictionary
1303 (1, ID, { values }) update the linked record with id = ID (write *values* on it)
1304 (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)
1307 [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1310 if self._type == 'many2many':
1311 assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1314 elif self._type == 'one2many':
1317 relation_obj = obj.pool.get(self.relation)
1319 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1321 read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1323 relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1325 relation_obj.unlink(cr, uid, vals[1], context=context)
1326 read_value.remove(vals[1])
1331 def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1332 if not type(ids) == list:
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)
1339 # simply delete the key to unset it.
1340 serialized.pop(field_name, None)
1342 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1343 obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1346 def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1348 records = obj.browse(cr, uid, ids, context=context)
1349 for record in records:
1350 # grab serialized value as object - already deserialized
1351 serialized = getattr(record, self.serialization_field)
1352 results[record.id] = {}
1353 for field_name in field_names:
1354 field_type = obj._columns[field_name]._type
1355 value = serialized.get(field_name, False)
1356 if field_type in ('one2many','many2many'):
1359 # filter out deleted records as superuser
1360 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1361 value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1362 if type(value) in (int,long) and field_type == 'many2one':
1363 relation_obj = obj.pool.get(obj._columns[field_name].relation)
1364 # check for deleted record as superuser
1365 if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1367 results[record.id][field_name] = value
1370 def __init__(self, serialization_field, **kwargs):
1371 self.serialization_field = serialization_field
1372 return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1376 # ---------------------------------------------------------
1378 # ---------------------------------------------------------
1380 class dummy(function):
1381 def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1384 def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1387 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1390 def __init__(self, *arg, **args):
1392 self._relations = []
1393 super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1395 # ---------------------------------------------------------
1397 # ---------------------------------------------------------
1399 class serialized(_column):
1400 """ A field able to store an arbitrary python data structure.
1402 Note: only plain components allowed.
1405 def _symbol_set_struct(val):
1406 return simplejson.dumps(val)
1408 def _symbol_get_struct(self, val):
1409 return simplejson.loads(val or '{}')
1412 _type = 'serialized'
1415 _symbol_f = _symbol_set_struct
1416 _symbol_set = (_symbol_c, _symbol_f)
1417 _symbol_get = _symbol_get_struct
1419 # TODO: review completly this class for speed improvement
1420 class property(function):
1422 def _get_default(self, obj, cr, uid, prop_name, context=None):
1423 return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1425 def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1426 """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1428 :param list of string prop_names: list of name of property fields for those we want the default value
1429 :return: map of property field names to their default value
1432 prop = obj.pool.get('ir.property')
1434 for prop_name in prop_names:
1435 res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1438 def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1439 prop = obj.pool.get('ir.property')
1440 vids = [obj._name + ',' + str(oid) for oid in ids]
1442 domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1443 #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1445 domain = [('res_id', 'in', vids)] + domain
1446 return prop.search(cr, uid, domain, context=context)
1448 # TODO: to rewrite more clean
1449 def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1453 nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1455 cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1457 default_val = self._get_default(obj, cr, uid, prop_name, context)
1459 property_create = False
1460 if isinstance(default_val, openerp.osv.orm.browse_record):
1461 if default_val.id != id_val:
1462 property_create = True
1463 elif default_val != id_val:
1464 property_create = True
1467 def_id = self._field_get(cr, uid, obj._name, prop_name)
1468 company = obj.pool.get('res.company')
1469 cid = company._company_default_get(cr, uid, obj._name, def_id,
1471 propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1473 prop = obj.pool.get('ir.property')
1474 return prop.create(cr, uid, {
1475 'name': propdef.name,
1477 'res_id': obj._name+','+str(id),
1479 'fields_id': def_id,
1484 def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1485 prop = obj.pool.get('ir.property')
1486 # get the default values (for res_id = False) for the property fields
1487 default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1489 # build the dictionary that will be returned
1492 res[id] = default_val.copy()
1494 for prop_name in prop_names:
1495 property_field = obj._all_columns.get(prop_name).column
1496 property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1497 # If the property field is a m2o field, we will append the id of the value to name_get_ids
1498 # in order to make a name_get in batch for all the ids needed.
1501 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1502 obj_reference = obj._name + ',' + str(id)
1503 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1505 res[id][prop_name] = value
1506 # Check existence as root (as seeing the name of a related
1507 # object depends on access right of source document,
1508 # not target, so user may not have access) in order to avoid
1509 # pointing on an unexisting record.
1510 if property_destination_obj:
1511 if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1512 name_get_ids[id] = res[id][prop_name].id
1514 res[id][prop_name] = False
1515 if property_destination_obj:
1516 # name_get as root (as seeing the name of a related
1517 # object depends on access right of source document,
1518 # not target, so user may not have access.)
1519 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1520 # the property field is a m2o, we need to return a tuple with (id, name)
1521 for k, v in name_get_ids.iteritems():
1522 if res[k][prop_name]:
1523 res[k][prop_name] = (v , name_get_values.get(v))
1526 def _field_get(self, cr, uid, model_name, prop):
1527 if not self.field_id.get(cr.dbname):
1528 cr.execute('SELECT id \
1529 FROM ir_model_fields \
1530 WHERE name=%s AND model=%s', (prop, model_name))
1532 self.field_id[cr.dbname] = res and res[0]
1533 return self.field_id[cr.dbname]
1535 def __init__(self, obj_prop, **args):
1536 # TODO remove obj_prop parameter (use many2one type)
1538 function.__init__(self, self._fnct_read, False, self._fnct_write,
1539 obj_prop, multi='properties', **args)
1545 def field_to_dict(model, cr, user, field, context=None):
1546 """ Return a dictionary representation of a field.
1548 The string, help, and selection attributes (if any) are untranslated. This
1549 representation is the one returned by fields_get() (fields_get() will do
1554 res = {'type': field._type}
1555 # This additional attributes for M2M and function field is added
1556 # because we need to display tooltip with this additional information
1557 # when client is started in debug mode.
1558 if isinstance(field, function):
1559 res['function'] = field._fnct and field._fnct.func_name or False
1560 res['store'] = field.store
1561 if isinstance(field.store, dict):
1562 res['store'] = str(field.store)
1563 res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1564 res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1565 res['fnct_inv_arg'] = field._fnct_inv_arg or False
1566 res['func_obj'] = field._obj or False
1567 if isinstance(field, many2many):
1568 (table, col1, col2) = field._sql_names(model)
1569 res['related_columns'] = [col1, col2]
1570 res['third_table'] = table
1571 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1572 'change_default', 'translate', 'help', 'select', 'selectable'):
1573 if getattr(field, arg):
1574 res[arg] = getattr(field, arg)
1575 for arg in ('digits', 'invisible', 'filters'):
1576 if getattr(field, arg, None):
1577 res[arg] = getattr(field, arg)
1580 res['string'] = field.string
1582 res['help'] = field.help
1584 if hasattr(field, 'selection'):
1585 if isinstance(field.selection, (tuple, list)):
1586 res['selection'] = field.selection
1588 # call the 'dynamic selection' function
1589 res['selection'] = field.selection(model, cr, user, context)
1590 if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1591 res['relation'] = field._obj
1592 res['domain'] = field._domain
1593 res['context'] = field._context
1595 if isinstance(field, one2many):
1596 res['relation_field'] = field._fields_id
1601 class column_info(object):
1602 """Struct containing details about an osv column, either one local to
1603 its model, or one inherited via _inherits.
1605 :attr name: name of the column
1606 :attr column: column instance, subclass of osv.fields._column
1607 :attr parent_model: if the column is inherited, name of the model
1608 that contains it, None for local columns.
1609 :attr parent_column: the name of the column containing the m2o
1610 relationship to the parent model that contains
1611 this column, None for local columns.
1612 :attr original_parent: if the column is inherited, name of the original
1613 parent model that contains it i.e in case of multilevel
1614 inheritence, None for local columns.
1616 def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1618 self.column = column
1619 self.parent_model = parent_model
1620 self.parent_column = parent_column
1621 self.original_parent = original_parent
1623 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: