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