6619e66b075fb8d42366897f57668127649c7fbb
[odoo/odoo.git] / openerp / osv / fields.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 """ Fields:
23       - simple
24       - relations (one2many, many2one, many2many)
25       - function
26
27     Fields Attributes:
28         * _classic_read: is a classic sql fields
29         * _type   : field type
30         * readonly
31         * required
32         * size
33 """
34
35 import base64
36 import datetime as DT
37 import re
38 import string
39 import sys
40 import warnings
41 import xmlrpclib
42 from psycopg2 import Binary
43
44 import openerp.netsvc as netsvc
45 import openerp.tools as tools
46 from openerp.tools.translate import _
47
48 def _symbol_set(symb):
49     if symb == None or symb == False:
50         return None
51     elif isinstance(symb, unicode):
52         return symb.encode('utf-8')
53     return str(symb)
54
55
56 class _column(object):
57     """ Base of all fields, a database column
58
59         An instance of this object is a *description* of a database column. It will
60         not hold any data, but only provide the methods to manipulate data of an
61         ORM record or even prepare/update the database to hold such a field of data.
62     """
63     _classic_read = True
64     _classic_write = True
65     _prefetch = True
66     _properties = False
67     _type = 'unknown'
68     _obj = None
69     _multi = False
70     _symbol_c = '%s'
71     _symbol_f = _symbol_set
72     _symbol_set = (_symbol_c, _symbol_f)
73     _symbol_get = None
74
75     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):
76         """
77
78         The 'manual' keyword argument specifies if the field is a custom one.
79         It corresponds to the 'state' column in ir_model_fields.
80
81         """
82         if domain is None:
83             domain = []
84         if context is None:
85             context = {}
86         self.states = states or {}
87         self.string = string
88         self.readonly = readonly
89         self.required = required
90         self.size = size
91         self.help = args.get('help', '')
92         self.priority = priority
93         self.change_default = change_default
94         self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
95         self.translate = translate
96         self._domain = domain
97         self._context = context
98         self.write = False
99         self.read = False
100         self.view_load = 0
101         self.select = select
102         self.manual = manual
103         self.selectable = True
104         self.group_operator = args.get('group_operator', False)
105         for a in args:
106             if args[a]:
107                 setattr(self, a, args[a])
108
109     def restart(self):
110         pass
111
112     def set(self, cr, obj, id, name, value, user=None, context=None):
113         cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
114
115     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
116         raise Exception(_('undefined get method !'))
117
118     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
119         ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
120         res = obj.read(cr, uid, ids, [name], context=context)
121         return [x[name] for x in res]
122
123
124 # ---------------------------------------------------------
125 # Simple fields
126 # ---------------------------------------------------------
127 class boolean(_column):
128     _type = 'boolean'
129     _symbol_c = '%s'
130     _symbol_f = lambda x: x and 'True' or 'False'
131     _symbol_set = (_symbol_c, _symbol_f)
132
133 class integer(_column):
134     _type = 'integer'
135     _symbol_c = '%s'
136     _symbol_f = lambda x: int(x or 0)
137     _symbol_set = (_symbol_c, _symbol_f)
138     _symbol_get = lambda self,x: x or 0
139
140 class integer_big(_column):
141     """Experimental 64 bit integer column type, currently unused.
142
143        TODO: this field should work fine for values up
144              to 32 bits, but greater values will not fit
145              in the XML-RPC int type, so a specific
146              get() method is needed to pass them as floats,
147              like what we do for integer functional fields.
148     """
149     _type = 'integer_big'
150     # do not reference the _symbol_* of integer class, as that would possibly
151     # unbind the lambda functions
152     _symbol_c = '%s'
153     _symbol_f = lambda x: int(x or 0)
154     _symbol_set = (_symbol_c, _symbol_f)
155     _symbol_get = lambda self,x: x or 0
156
157 class reference(_column):
158     _type = 'reference'
159     def __init__(self, string, selection, size, **args):
160         _column.__init__(self, string=string, size=size, selection=selection, **args)
161
162
163 class char(_column):
164     _type = 'char'
165
166     def __init__(self, string, size, **args):
167         _column.__init__(self, string=string, size=size, **args)
168         self._symbol_set = (self._symbol_c, self._symbol_set_char)
169
170     # takes a string (encoded in utf8) and returns a string (encoded in utf8)
171     def _symbol_set_char(self, symb):
172         #TODO:
173         # * we need to remove the "symb==False" from the next line BUT
174         #   for now too many things rely on this broken behavior
175         # * the symb==None test should be common to all data types
176         if symb == None or symb == False:
177             return None
178
179         # we need to convert the string to a unicode object to be able
180         # to evaluate its length (and possibly truncate it) reliably
181         u_symb = tools.ustr(symb)
182
183         return u_symb[:self.size].encode('utf8')
184
185
186 class text(_column):
187     _type = 'text'
188
189 import __builtin__
190
191 class float(_column):
192     _type = 'float'
193     _symbol_c = '%s'
194     _symbol_f = lambda x: __builtin__.float(x or 0.0)
195     _symbol_set = (_symbol_c, _symbol_f)
196     _symbol_get = lambda self,x: x or 0.0
197
198     def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
199         _column.__init__(self, string=string, **args)
200         self.digits = digits
201         self.digits_compute = digits_compute
202
203
204     def digits_change(self, cr):
205         if self.digits_compute:
206             t = self.digits_compute(cr)
207             self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
208             self.digits = t
209
210 class date(_column):
211     _type = 'date'
212     @staticmethod
213     def today(*args):
214         """ Returns the current date in a format fit for being a
215         default value to a ``date`` field.
216
217         This method should be provided as is to the _defaults dict, it
218         should not be called.
219         """
220         return DT.date.today().strftime(
221             tools.DEFAULT_SERVER_DATE_FORMAT)
222
223 class datetime(_column):
224     _type = 'datetime'
225     @staticmethod
226     def now(*args):
227         """ Returns the current datetime in a format fit for being a
228         default value to a ``datetime`` field.
229
230         This method should be provided as is to the _defaults dict, it
231         should not be called.
232         """
233         return DT.datetime.now().strftime(
234             tools.DEFAULT_SERVER_DATETIME_FORMAT)
235
236 class time(_column):
237     _type = 'time'
238     @staticmethod
239     def now( *args):
240         """ Returns the current time in a format fit for being a
241         default value to a ``time`` field.
242
243         This method should be proivided as is to the _defaults dict,
244         it should not be called.
245         """
246         return DT.datetime.now().strftime(
247             tools.DEFAULT_SERVER_TIME_FORMAT)
248
249 class binary(_column):
250     _type = 'binary'
251     _symbol_c = '%s'
252     _symbol_f = lambda symb: symb and Binary(symb) or None
253     _symbol_set = (_symbol_c, _symbol_f)
254     _symbol_get = lambda self, x: x and str(x)
255
256     _classic_read = False
257     _prefetch = False
258
259     def __init__(self, string='unknown', filters=None, **args):
260         _column.__init__(self, string=string, **args)
261         self.filters = filters
262
263     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
264         if not context:
265             context = {}
266         if not values:
267             values = []
268         res = {}
269         for i in ids:
270             val = None
271             for v in values:
272                 if v['id'] == i:
273                     val = v[name]
274                     break
275
276             # If client is requesting only the size of the field, we return it instead
277             # of the content. Presumably a separate request will be done to read the actual
278             # content if it's needed at some point.
279             # TODO: after 6.0 we should consider returning a dict with size and content instead of
280             #       having an implicit convention for the value
281             if val and context.get('bin_size_%s' % name, context.get('bin_size')):
282                 res[i] = tools.human_size(long(val))
283             else:
284                 res[i] = val
285         return res
286
287 class selection(_column):
288     _type = 'selection'
289
290     def __init__(self, selection, string='unknown', **args):
291         _column.__init__(self, string=string, **args)
292         self.selection = selection
293
294 # ---------------------------------------------------------
295 # Relationals fields
296 # ---------------------------------------------------------
297
298 #
299 # Values: (0, 0,  { fields })    create
300 #         (1, ID, { fields })    update
301 #         (2, ID)                remove (delete)
302 #         (3, ID)                unlink one (target id or target of relation)
303 #         (4, ID)                link
304 #         (5)                    unlink all (only valid for one2many)
305 #
306 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
307 class one2one(_column):
308     _classic_read = False
309     _classic_write = True
310     _type = 'one2one'
311
312     def __init__(self, obj, string='unknown', **args):
313         warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
314         _column.__init__(self, string=string, **args)
315         self._obj = obj
316
317     def set(self, cr, obj_src, id, field, act, user=None, context=None):
318         if not context:
319             context = {}
320         obj = obj_src.pool.get(self._obj)
321         self._table = obj_src.pool.get(self._obj)._table
322         if act[0] == 0:
323             id_new = obj.create(cr, user, act[1])
324             cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
325         else:
326             cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
327             id = cr.fetchone()[0]
328             obj.write(cr, user, [id], act[1], context=context)
329
330     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
331         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
332
333
334 class many2one(_column):
335     _classic_read = False
336     _classic_write = True
337     _type = 'many2one'
338     _symbol_c = '%s'
339     _symbol_f = lambda x: x or None
340     _symbol_set = (_symbol_c, _symbol_f)
341
342     def __init__(self, obj, string='unknown', **args):
343         _column.__init__(self, string=string, **args)
344         self._obj = obj
345
346     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
347         if context is None:
348             context = {}
349         if values is None:
350             values = {}
351
352         res = {}
353         for r in values:
354             res[r['id']] = r[name]
355         for id in ids:
356             res.setdefault(id, '')
357         obj = obj.pool.get(self._obj)
358
359         # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
360         # we use uid=1 because the visibility of a many2one field value (just id and name)
361         # must be the access right of the parent form and not the linked object itself.
362         records = dict(obj.name_get(cr, 1,
363                                     list(set([x for x in res.values() if isinstance(x, (int,long))])),
364                                     context=context))
365         for id in res:
366             if res[id] in records:
367                 res[id] = (res[id], records[res[id]])
368             else:
369                 res[id] = False
370         return res
371
372     def set(self, cr, obj_src, id, field, values, user=None, context=None):
373         if not context:
374             context = {}
375         obj = obj_src.pool.get(self._obj)
376         self._table = obj_src.pool.get(self._obj)._table
377         if type(values) == type([]):
378             for act in values:
379                 if act[0] == 0:
380                     id_new = obj.create(cr, act[2])
381                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
382                 elif act[0] == 1:
383                     obj.write(cr, [act[1]], act[2], context=context)
384                 elif act[0] == 2:
385                     cr.execute('delete from '+self._table+' where id=%s', (act[1],))
386                 elif act[0] == 3 or act[0] == 5:
387                     cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
388                 elif act[0] == 4:
389                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
390         else:
391             if values:
392                 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
393             else:
394                 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
395
396     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
397         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
398
399
400 class one2many(_column):
401     _classic_read = False
402     _classic_write = False
403     _prefetch = False
404     _type = 'one2many'
405
406     def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
407         _column.__init__(self, string=string, **args)
408         self._obj = obj
409         self._fields_id = fields_id
410         self._limit = limit
411         #one2many can't be used as condition for defaults
412         assert(self.change_default != True)
413
414     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
415         if context is None:
416             context = {}
417         if self._context:
418             context = context.copy()
419         context.update(self._context)
420         if values is None:
421             values = {}
422
423         res = {}
424         for id in ids:
425             res[id] = []
426
427         ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
428         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
429             if r[self._fields_id] in res:
430                 res[r[self._fields_id]].append(r['id'])
431         return res
432
433     def set(self, cr, obj, id, field, values, user=None, context=None):
434         result = []
435         if not context:
436             context = {}
437         if self._context:
438             context = context.copy()
439         context.update(self._context)
440         context['no_store_function'] = True
441         if not values:
442             return
443         _table = obj.pool.get(self._obj)._table
444         obj = obj.pool.get(self._obj)
445         for act in values:
446             if act[0] == 0:
447                 act[2][self._fields_id] = id
448                 id_new = obj.create(cr, user, act[2], context=context)
449                 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
450             elif act[0] == 1:
451                 obj.write(cr, user, [act[1]], act[2], context=context)
452             elif act[0] == 2:
453                 obj.unlink(cr, user, [act[1]], context=context)
454             elif act[0] == 3:
455                 reverse_rel = obj._all_columns.get(self._fields_id)
456                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
457                 # if the model has on delete cascade, just delete the row
458                 if reverse_rel.column.ondelete == "cascade":
459                     obj.unlink(cr, user, [act[1]], context=context)
460                 else:
461                     cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
462             elif act[0] == 4:
463                 # Must use write() to recompute parent_store structure if needed
464                 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
465             elif act[0] == 5:
466                 reverse_rel = obj._all_columns.get(self._fields_id)
467                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
468                 # if the model has on delete cascade, just delete the rows
469                 if reverse_rel.column.ondelete == "cascade":
470                     obj.unlink(cr, user, obj.search(cr, user, [(self._fields_id,'=',id)], context=context), context=context)
471                 else:
472                     cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
473             elif act[0] == 6:
474                 # Must use write() to recompute parent_store structure if needed
475                 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
476                 ids2 = act[2] or [0]
477                 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
478                 ids3 = map(lambda x:x[0], cr.fetchall())
479                 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
480         return result
481
482     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
483         return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
484
485
486 #
487 # Values: (0, 0,  { fields })    create
488 #         (1, ID, { fields })    update (write fields to ID)
489 #         (2, ID)                remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
490 #         (3, ID)                unlink (delete the relationship between the two objects but does not delete ID)
491 #         (4, ID)                link (add a relationship)
492 #         (5, ID)                unlink all
493 #         (6, ?, ids)            set a list of links
494 #
495 class many2many(_column):
496     """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
497        low-level details of the intermediary relationship table transparently.
498
499        :param str obj: destination model
500        :param str rel: optional name of the intermediary relationship table. If not specified,
501                        a canonical name will be derived based on the alphabetically-ordered
502                        model names of the source and destination (in the form: ``amodel_bmodel_rel``).
503                        Automatic naming is not possible when the source and destination are
504                        the same, for obvious ambiguity reasons.
505        :param str id1: optional name for the column holding the foreign key to the current
506                        model in the relationship table. If not specified, a canonical name
507                        will be derived based on the model name (in the form: `src_model_id`).
508        :param str id2: optional name for the column holding the foreign key to the destination
509                        model in the relationship table. If not specified, a canonical name
510                        will be derived based on the model name (in the form: `dest_model_id`)
511        :param str string: field label
512     """
513     _classic_read = False
514     _classic_write = False
515     _prefetch = False
516     _type = 'many2many'
517
518     def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
519         """ 
520         """
521         _column.__init__(self, string=string, **args)
522         self._obj = obj
523         if rel and '.' in rel:
524             raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
525                 'You used %s, which is not a valid SQL table name.')% (string,rel))
526         self._rel = rel
527         self._id1 = id1
528         self._id2 = id2
529         self._limit = limit
530
531     def _sql_names(self, source_model):
532         """Return the SQL names defining the structure of the m2m relationship table
533             
534             :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
535                      local_col is the name of the column holding the current model's FK, and
536                      dest_col is the name of the column holding the destination model's FK, and
537         """
538         tbl, col1, col2 = self._rel, self._id1, self._id2
539         if not all((tbl, col1, col2)):
540             # the default table name is based on the stable alphabetical order of tables
541             dest_model = source_model.pool.get(self._obj)
542             tables = tuple(sorted([source_model._table, dest_model._table]))
543             if not tbl:
544                 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
545                                                'is not possible when source and destination models are '\
546                                                'the same'
547                 tbl = '%s_%s_rel' % tables
548             if not col1:
549                 col1 = '%s_id' % source_model._table
550             if not col2:
551                 col2 = '%s_id' % dest_model._table
552         return (tbl, col1, col2)
553
554     def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
555         if not context:
556             context = {}
557         if not values:
558             values = {}
559         res = {}
560         if not ids:
561             return res
562         for id in ids:
563             res[id] = []
564         if offset:
565             warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
566                       DeprecationWarning, stacklevel=2)
567         obj = model.pool.get(self._obj)
568         rel, id1, id2 = self._sql_names(model)
569
570         # static domains are lists, and are evaluated both here and on client-side, while string
571         # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
572         # FIXME: make this distinction explicit in API!
573         domain = isinstance(self._domain, list) and self._domain or []
574
575         wquery = obj._where_calc(cr, user, domain, context=context)
576         obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
577         from_c, where_c, where_params = wquery.get_sql()
578         if where_c:
579             where_c = ' AND ' + where_c
580
581         if offset or self._limit:
582             order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
583         else:
584             order_by = ''
585
586         limit_str = ''
587         if self._limit is not None:
588             limit_str = ' LIMIT %d' % self._limit
589
590         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
591                    FROM %(rel)s, %(from_c)s \
592                   WHERE %(rel)s.%(id1)s IN %%s \
593                     AND %(rel)s.%(id2)s = %(tbl)s.id \
594                  %(where_c)s  \
595                  %(order_by)s \
596                  %(limit)s \
597                  OFFSET %(offset)d' \
598             % {'rel': rel,
599                'from_c': from_c,
600                'tbl': obj._table,
601                'id1': id1,
602                'id2': id2,
603                'where_c': where_c,
604                'limit': limit_str,
605                'order_by': order_by,
606                'offset': offset,
607               }
608         cr.execute(query, [tuple(ids),] + where_params)
609         for r in cr.fetchall():
610             res[r[1]].append(r[0])
611         return res
612
613     def set(self, cr, model, id, name, values, user=None, context=None):
614         if not context:
615             context = {}
616         if not values:
617             return
618         rel, id1, id2 = self._sql_names(model)
619         obj = model.pool.get(self._obj)
620         for act in values:
621             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
622                 continue
623             if act[0] == 0:
624                 idnew = obj.create(cr, user, act[2], context=context)
625                 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
626             elif act[0] == 1:
627                 obj.write(cr, user, [act[1]], act[2], context=context)
628             elif act[0] == 2:
629                 obj.unlink(cr, user, [act[1]], context=context)
630             elif act[0] == 3:
631                 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
632             elif act[0] == 4:
633                 # following queries are in the same transaction - so should be relatively safe
634                 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
635                 if not cr.fetchone():
636                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
637             elif act[0] == 5:
638                 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
639             elif act[0] == 6:
640
641                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
642                 if d1:
643                     d1 = ' and ' + ' and '.join(d1)
644                 else:
645                     d1 = ''
646                 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)
647
648                 for act_nbr in act[2]:
649                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
650
651     #
652     # TODO: use a name_search
653     #
654     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
655         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
656
657
658 def get_nice_size(value):
659     size = 0
660     if isinstance(value, (int,long)):
661         size = value
662     elif value: # this is supposed to be a string
663         size = len(value)
664     return tools.human_size(size)
665
666 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
667 # and http://bugs.python.org/issue10066
668 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
669
670 def sanitize_binary_value(value):
671     # binary fields should be 7-bit ASCII base64-encoded data,
672     # but we do additional sanity checks to make sure the values
673     # are not something else that won't pass via XML-RPC
674     if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
675         # these builtin types are meant to pass untouched
676         return value
677
678     # Handle invalid bytes values that will cause problems
679     # for XML-RPC. See for more info:
680     #  - http://bugs.python.org/issue10066
681     #  - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
682
683     # Coercing to unicode would normally allow it to properly pass via
684     # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
685     # (this works for _any_ byte values, thanks to the fallback
686     #  to latin-1 passthrough encoding when decoding to unicode)
687     value = tools.ustr(value)
688
689     # Due to Python bug #10066 this could still yield invalid XML
690     # bytes, specifically in the low byte range, that will crash
691     # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
692     # So check for low bytes values, and if any, perform
693     # base64 encoding - not very smart or useful, but this is
694     # our last resort to avoid crashing the request.
695     if invalid_xml_low_bytes.search(value):
696         # b64-encode after restoring the pure bytes with latin-1
697         # passthrough encoding
698         value = base64.b64encode(value.encode('latin-1'))
699
700     return value
701
702
703 # ---------------------------------------------------------
704 # Function fields
705 # ---------------------------------------------------------
706 class function(_column):
707     """
708     A field whose value is computed by a function (rather
709     than being read from the database).
710
711     :param fnct: the callable that will compute the field value.
712     :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
713     :param fnct_inv: the callable that will allow writing values in that field
714                      (if not provided, the field is read-only).
715     :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
716                          writing a value.
717     :param str type: type of the field simulated by the function field
718     :param fnct_search: the callable that allows searching on the field
719                         (if not provided, search will not return any result).
720     :param store: store computed value in database
721                   (see :ref:`The *store* parameter <field-function-store>`).
722     :type store: True or dict specifying triggers for field computation
723     :param multi: name of batch for batch computation of function fields.
724                   All fields with the same batch name will be computed by
725                   a single function call. This changes the signature of the
726                   ``fnct`` callable.
727
728     .. _field-function-fnct: The ``fnct`` parameter
729
730     .. rubric:: The ``fnct`` parameter
731
732     The callable implementing the function field must have the following signature:
733
734     .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
735
736         Implements the function field.
737
738         :param orm model: model to which the field belongs (should be ``self`` for
739                           a model method)
740         :param field_name(s): name of the field to compute, or if ``multi`` is provided,
741                               list of field names to compute.
742         :type field_name(s): str | [str]
743         :param arg: arbitrary value passed when declaring the function field
744         :rtype: dict
745         :return: mapping of ``ids`` to computed values, or if multi is provided,
746                  to a map of field_names to computed values
747
748     The values in the returned dictionary must be of the type specified by the type
749     argument in the field declaration.
750
751     Here is an example with a simple function ``char`` function field::
752
753         # declarations
754         def compute(self, cr, uid, ids, field_name, arg, context):
755             result = {}
756             # ...
757             return result
758         _columns['my_char'] = fields.function(compute, type='char', size=50)
759
760         # when called with ``ids=[1,2,3]``, ``compute`` could return:
761         {
762             1: 'foo',
763             2: 'bar',
764             3: False # null values should be returned explicitly too
765         }
766
767     If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
768     of the field names that should be computed. Each value in the returned
769     dictionary must then be a dictionary mapping field names to values.
770
771     Here is an example where two function fields (``name`` and ``age``)
772     are both computed by a single function field::
773
774         # declarations
775         def compute(self, cr, uid, ids, field_names, arg, context):
776             result = {}
777             # ...
778             return result
779         _columns['name'] = fields.function(compute_person_data, type='char',\
780                                            size=50, multi='person_data')
781         _columns[''age'] = fields.function(compute_person_data, type='integer',\
782                                            multi='person_data')
783
784         # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
785         {
786             1: {'name': 'Bob', 'age': 23},
787             2: {'name': 'Sally', 'age': 19},
788             3: {'name': 'unknown', 'age': False}
789         }
790
791     .. _field-function-fnct-inv:
792
793     .. rubric:: The ``fnct_inv`` parameter
794
795     This callable implements the write operation for the function field
796     and must have the following signature:
797
798     .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
799
800         Callable that implements the ``write`` operation for the function field.
801
802         :param orm model: model to which the field belongs (should be ``self`` for
803                           a model method)
804         :param int id: the identifier of the object to write on
805         :param str field_name: name of the field to set
806         :param fnct_inv_arg: arbitrary value passed when declaring the function field
807         :return: True
808
809     When writing values for a function field, the ``multi`` parameter is ignored.
810
811     .. _field-function-fnct-search:
812
813     .. rubric:: The ``fnct_search`` parameter
814
815     This callable implements the search operation for the function field
816     and must have the following signature:
817
818     .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
819
820         Callable that implements the ``search`` operation for the function field by expanding
821         a search criterion based on the function field into a new domain based only on
822         columns that are stored in the database.
823
824         :param orm model: model to which the field belongs (should be ``self`` for
825                           a model method)
826         :param orm model_again: same value as ``model`` (seriously! this is for backwards
827                                 compatibility)
828         :param str field_name: name of the field to search on
829         :param list criterion: domain component specifying the search criterion on the field.
830         :rtype: list
831         :return: domain to use instead of ``criterion`` when performing the search.
832                  This new domain must be based only on columns stored in the database, as it
833                  will be used directly without any translation.
834
835         The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
836         The most generic way to implement ``fnct_search`` is to directly search for the records that
837         match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
838         ``[('id','in',[1,3,5])]``.
839
840     .. _field-function-store:
841
842     .. rubric:: The ``store`` parameter
843
844     The ``store`` parameter allows caching the result of the field computation in the
845     database, and defining the triggers that will invalidate that cache and force a
846     recomputation of the function field.
847     When not provided, the field is computed every time its value is read.
848     The value of ``store`` may be either ``True`` (to recompute the field value whenever
849     any field in the same record is modified), or a dictionary specifying a more
850     flexible set of recomputation triggers.
851
852     A trigger specification is a dictionary that maps the names of the models that
853     will trigger the computation, to a tuple describing the trigger rule, in the
854     following form::
855
856         store = {
857             'trigger_model': (mapping_function,
858                               ['trigger_field1', 'trigger_field2'],
859                               priority),
860         }
861
862     A trigger rule is defined by a 3-item tuple where:
863
864         * The ``mapping_function`` is defined as follows:
865
866             .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
867
868                 Callable that maps record ids of a trigger model to ids of the
869                 corresponding records in the source model (whose field values
870                 need to be recomputed).
871
872                 :param orm model: trigger_model
873                 :param list trigger_ids: ids of the records of trigger_model that were
874                                          modified
875                 :rtype: list
876                 :return: list of ids of the source model whose function field values
877                          need to be recomputed
878
879         * The second item is a list of the fields who should act as triggers for
880           the computation. If an empty list is given, all fields will act as triggers.
881         * The last item is the priority, used to order the triggers when processing them
882           after any write operation on a model that has function field triggers. The
883           default priority is 10.
884
885     In fact, setting store = True is the same as using the following trigger dict::
886
887         store = {
888               'model_itself': (lambda self, cr, uid, ids, context: ids,
889                                [],
890                                10)
891         }
892
893     """
894     _classic_read = False
895     _classic_write = False
896     _prefetch = False
897     _type = 'function'
898     _properties = True
899
900 #
901 # multi: compute several fields in one call
902 #
903     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):
904         _column.__init__(self, **args)
905         self._obj = obj
906         self._fnct = fnct
907         self._fnct_inv = fnct_inv
908         self._arg = arg
909         self._multi = multi
910         if 'relation' in args:
911             self._obj = args['relation']
912
913         self.digits = args.get('digits', (16,2))
914         self.digits_compute = args.get('digits_compute', None)
915
916         self._fnct_inv_arg = fnct_inv_arg
917         if not fnct_inv:
918             self.readonly = 1
919         self._type = type
920         self._fnct_search = fnct_search
921         self.store = store
922
923         if not fnct_search and not store:
924             self.selectable = False
925
926         if store:
927             if self._type != 'many2one':
928                 # m2o fields need to return tuples with name_get, not just foreign keys
929                 self._classic_read = True
930             self._classic_write = True
931             if type=='binary':
932                 self._symbol_get=lambda x:x and str(x)
933
934         if type == 'float':
935             self._symbol_c = float._symbol_c
936             self._symbol_f = float._symbol_f
937             self._symbol_set = float._symbol_set
938
939         if type == 'boolean':
940             self._symbol_c = boolean._symbol_c
941             self._symbol_f = boolean._symbol_f
942             self._symbol_set = boolean._symbol_set
943
944         if type in ['integer','integer_big']:
945             self._symbol_c = integer._symbol_c
946             self._symbol_f = integer._symbol_f
947             self._symbol_set = integer._symbol_set
948
949     def digits_change(self, cr):
950         if self.digits_compute:
951             t = self.digits_compute(cr)
952             self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
953             self.digits = t
954
955
956     def search(self, cr, uid, obj, name, args, context=None):
957         if not self._fnct_search:
958             #CHECKME: should raise an exception
959             return []
960         return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
961
962     def postprocess(self, cr, uid, obj, field, value=None, context=None):
963         if context is None:
964             context = {}
965         result = value
966         field_type = obj._columns[field]._type
967         if field_type == "many2one":
968             # make the result a tuple if it is not already one
969             if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
970                 obj_model = obj.pool.get(obj._columns[field].relation)
971                 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
972                 result = (value, dict_names[value])
973
974         if field_type == 'binary':
975             if context.get('bin_size', False):
976                 # client requests only the size of binary fields
977                 result = get_nice_size(value)
978             else:
979                 result = sanitize_binary_value(value)
980
981         if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
982             # integer/long values greater than 2^31-1 are not supported
983             # in pure XMLRPC, so we have to pass them as floats :-(
984             # This is not needed for stored fields and non-functional integer
985             # fields, as their values are constrained by the database backend
986             # to the same 32bits signed int limit.
987             result = float(value)
988         return result
989
990     def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
991         result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
992         for id in ids:
993             if self._multi and id in result:
994                 for field, value in result[id].iteritems():
995                     if value:
996                         result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
997             elif result.get(id):
998                 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
999         return result
1000
1001     def set(self, cr, obj, id, name, value, user=None, context=None):
1002         if not context:
1003             context = {}
1004         if self._fnct_inv:
1005             self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1006
1007 # ---------------------------------------------------------
1008 # Related fields
1009 # ---------------------------------------------------------
1010
1011 class related(function):
1012     """Field that points to some data inside another field of the current record.
1013
1014     Example::
1015
1016        _columns = {
1017            'foo_id': fields.many2one('my.foo', 'Foo'),
1018            'bar': fields.related('frol', 'foo_id', type='char', string='Frol of Foo'),
1019         }
1020     """
1021
1022     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1023         self._field_get2(cr, uid, obj, context)
1024         i = len(self._arg)-1
1025         sarg = name
1026         while i>0:
1027             if type(sarg) in [type([]), type( (1,) )]:
1028                 where = [(self._arg[i], 'in', sarg)]
1029             else:
1030                 where = [(self._arg[i], '=', sarg)]
1031             if domain:
1032                 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1033                 domain = []
1034             sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1035             i -= 1
1036         return [(self._arg[0], 'in', sarg)]
1037
1038     def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1039         self._field_get2(cr, uid, obj, context=context)
1040         if type(ids) != type([]):
1041             ids=[ids]
1042         objlst = obj.browse(cr, uid, ids)
1043         for data in objlst:
1044             t_id = data.id
1045             t_data = data
1046             for i in range(len(self.arg)):
1047                 if not t_data: break
1048                 field_detail = self._relations[i]
1049                 if not t_data[self.arg[i]]:
1050                     if self._type not in ('one2many', 'many2many'):
1051                         t_id = t_data['id']
1052                     t_data = False
1053                 elif field_detail['type'] in ('one2many', 'many2many'):
1054                     if self._type != "many2one":
1055                         t_id = t_data.id
1056                         t_data = t_data[self.arg[i]][0]
1057                     else:
1058                         t_data = False
1059                 else:
1060                     t_id = t_data['id']
1061                     t_data = t_data[self.arg[i]]
1062             else:
1063                 model = obj.pool.get(self._relations[-1]['object'])
1064                 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1065
1066     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1067         self._field_get2(cr, uid, obj, context)
1068         if not ids: return {}
1069         relation = obj._name
1070         if self._type in ('one2many', 'many2many'):
1071             res = dict([(i, []) for i in ids])
1072         else:
1073             res = {}.fromkeys(ids, False)
1074
1075         objlst = obj.browse(cr, 1, ids, context=context)
1076         for data in objlst:
1077             if not data:
1078                 continue
1079             t_data = data
1080             relation = obj._name
1081             for i in range(len(self.arg)):
1082                 field_detail = self._relations[i]
1083                 relation = field_detail['object']
1084                 try:
1085                     if not t_data[self.arg[i]]:
1086                         t_data = False
1087                         break
1088                 except:
1089                     t_data = False
1090                     break
1091                 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1092                     t_data = t_data[self.arg[i]][0]
1093                 elif t_data:
1094                     t_data = t_data[self.arg[i]]
1095             if type(t_data) == type(objlst[0]):
1096                 res[data.id] = t_data.id
1097             elif t_data:
1098                 res[data.id] = t_data
1099         if self._type=='many2one':
1100             ids = filter(None, res.values())
1101             if ids:
1102                 # name_get as root, as seeing the name of a related
1103                 # object depends on access right of source document,
1104                 # not target, so user may not have access.
1105                 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1106                 for r in res:
1107                     if res[r]:
1108                         res[r] = (res[r], ng[res[r]])
1109         elif self._type in ('one2many', 'many2many'):
1110             for r in res:
1111                 if res[r]:
1112                     res[r] = [x.id for x in res[r]]
1113         return res
1114
1115     def __init__(self, *arg, **args):
1116         self.arg = arg
1117         self._relations = []
1118         super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1119         if self.store is True:
1120             # TODO: improve here to change self.store = {...} according to related objects
1121             pass
1122
1123     def _field_get2(self, cr, uid, obj, context=None):
1124         if self._relations:
1125             return
1126         obj_name = obj._name
1127         for i in range(len(self._arg)):
1128             f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1129             self._relations.append({
1130                 'object': obj_name,
1131                 'type': f['type']
1132
1133             })
1134             if f.get('relation',False):
1135                 obj_name = f['relation']
1136                 self._relations[-1]['relation'] = f['relation']
1137
1138 # ---------------------------------------------------------
1139 # Dummy fields
1140 # ---------------------------------------------------------
1141
1142 class dummy(function):
1143     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1144         return []
1145
1146     def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1147         return False
1148
1149     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1150         return {}
1151
1152     def __init__(self, *arg, **args):
1153         self.arg = arg
1154         self._relations = []
1155         super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1156
1157 # ---------------------------------------------------------
1158 # Serialized fields
1159 # ---------------------------------------------------------
1160 class serialized(_column):
1161     def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
1162         self._serialize_func = serialize_func
1163         self._deserialize_func = deserialize_func
1164         self._type = type
1165         self._symbol_set = (self._symbol_c, self._serialize_func)
1166         self._symbol_get = self._deserialize_func
1167         super(serialized, self).__init__(string=string, **args)
1168
1169 # TODO: review completly this class for speed improvement
1170 class property(function):
1171
1172     def _get_default(self, obj, cr, uid, prop_name, context=None):
1173         return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1174
1175     def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1176         """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1177
1178            :param list of string prop_names: list of name of property fields for those we want the default value
1179            :return: map of property field names to their default value
1180            :rtype: dict
1181         """
1182         prop = obj.pool.get('ir.property')
1183         res = {}
1184         for prop_name in prop_names:
1185             res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1186         return res
1187
1188     def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1189         prop = obj.pool.get('ir.property')
1190         vids = [obj._name + ',' + str(oid) for oid in  ids]
1191
1192         domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1193         #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1194         if vids:
1195             domain = [('res_id', 'in', vids)] + domain
1196         return prop.search(cr, uid, domain, context=context)
1197
1198     # TODO: to rewrite more clean
1199     def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1200         if context is None:
1201             context = {}
1202
1203         nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1204         if nids:
1205             cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1206
1207         default_val = self._get_default(obj, cr, uid, prop_name, context)
1208
1209         if id_val is not default_val:
1210             def_id = self._field_get(cr, uid, obj._name, prop_name)
1211             company = obj.pool.get('res.company')
1212             cid = company._company_default_get(cr, uid, obj._name, def_id,
1213                                                context=context)
1214             propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1215                                                              context=context)
1216             prop = obj.pool.get('ir.property')
1217             return prop.create(cr, uid, {
1218                 'name': propdef.name,
1219                 'value': id_val,
1220                 'res_id': obj._name+','+str(id),
1221                 'company_id': cid,
1222                 'fields_id': def_id,
1223                 'type': self._type,
1224             }, context=context)
1225         return False
1226
1227     def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1228         prop = obj.pool.get('ir.property')
1229         # get the default values (for res_id = False) for the property fields
1230         default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1231
1232         # build the dictionary that will be returned
1233         res = {}
1234         for id in ids:
1235             res[id] = default_val.copy()
1236
1237         for prop_name in prop_names:
1238             property_field = obj._all_columns.get(prop_name).column
1239             property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1240             # If the property field is a m2o field, we will append the id of the value to name_get_ids
1241             # in order to make a name_get in batch for all the ids needed.
1242             name_get_ids = {}
1243             for id in ids:
1244                 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1245                 obj_reference = obj._name + ',' + str(id)
1246                 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1247                 if value:
1248                     res[id][prop_name] = value
1249                 # Check existence as root (as seeing the name of a related
1250                 # object depends on access right of source document,
1251                 # not target, so user may not have access) in order to avoid
1252                 # pointing on an unexisting record.
1253                 if property_destination_obj:
1254                     if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1255                         name_get_ids[id] = res[id][prop_name].id
1256                     else:
1257                         res[id][prop_name] = False
1258             if property_destination_obj:
1259                 # name_get as root (as seeing the name of a related
1260                 # object depends on access right of source document,
1261                 # not target, so user may not have access.)
1262                 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1263                 # the property field is a m2o, we need to return a tuple with (id, name)
1264                 for k, v in name_get_ids.iteritems():
1265                     if res[k][prop_name]:
1266                         res[k][prop_name] = (v , name_get_values.get(v))
1267         return res
1268
1269     def _field_get(self, cr, uid, model_name, prop):
1270         if not self.field_id.get(cr.dbname):
1271             cr.execute('SELECT id \
1272                     FROM ir_model_fields \
1273                     WHERE name=%s AND model=%s', (prop, model_name))
1274             res = cr.fetchone()
1275             self.field_id[cr.dbname] = res and res[0]
1276         return self.field_id[cr.dbname]
1277
1278     def __init__(self, obj_prop, **args):
1279         # TODO remove obj_prop parameter (use many2one type)
1280         self.field_id = {}
1281         function.__init__(self, self._fnct_read, False, self._fnct_write,
1282                           obj_prop, multi='properties', **args)
1283
1284     def restart(self):
1285         self.field_id = {}
1286
1287
1288 def field_to_dict(self, cr, user, context, field):
1289     """ Return a dictionary representation of a field.
1290
1291     The string, help, and selection attributes (if any) are untranslated.  This
1292     representation is the one returned by fields_get() (fields_get() will do
1293     the translation).
1294
1295     """
1296
1297     res = {'type': field._type}
1298     # This additional attributes for M2M and function field is added
1299     # because we need to display tooltip with this additional information
1300     # when client is started in debug mode.
1301     if isinstance(field, function):
1302         res['function'] = field._fnct and field._fnct.func_name or False
1303         res['store'] = field.store
1304         if isinstance(field.store, dict):
1305             res['store'] = str(field.store)
1306         res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1307         res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1308         res['fnct_inv_arg'] = field._fnct_inv_arg or False
1309         res['func_obj'] = field._obj or False
1310     if isinstance(field, many2many):
1311         res['related_columns'] = list((field._id1, field._id2))
1312         res['third_table'] = field._rel
1313     for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1314             'change_default', 'translate', 'help', 'select', 'selectable'):
1315         if getattr(field, arg):
1316             res[arg] = getattr(field, arg)
1317     for arg in ('digits', 'invisible', 'filters'):
1318         if getattr(field, arg, None):
1319             res[arg] = getattr(field, arg)
1320
1321     if field.string:
1322         res['string'] = field.string
1323     if field.help:
1324         res['help'] = field.help
1325
1326     if hasattr(field, 'selection'):
1327         if isinstance(field.selection, (tuple, list)):
1328             res['selection'] = field.selection
1329         else:
1330             # call the 'dynamic selection' function
1331             res['selection'] = field.selection(self, cr, user, context)
1332     if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1333         res['relation'] = field._obj
1334         res['domain'] = field._domain
1335         res['context'] = field._context
1336
1337     return res
1338
1339
1340 class column_info(object):
1341     """Struct containing details about an osv column, either one local to
1342        its model, or one inherited via _inherits.
1343
1344        :attr name: name of the column
1345        :attr column: column instance, subclass of osv.fields._column
1346        :attr parent_model: if the column is inherited, name of the model
1347                            that contains it, None for local columns.
1348        :attr parent_column: the name of the column containing the m2o
1349                             relationship to the parent model that contains
1350                             this column, None for local columns.
1351        :attr original_parent: if the column is inherited, name of the original
1352                             parent model that contains it i.e in case of multilevel
1353                             inheritence, None for local columns.
1354     """
1355     def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1356         self.name = name
1357         self.column = column
1358         self.parent_model = parent_model
1359         self.parent_column = parent_column
1360         self.original_parent = original_parent
1361
1362 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1363