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