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