[MRG] merge trunk.
[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 logging
38 import re
39 import xmlrpclib
40 from psycopg2 import Binary
41
42 import openerp
43 import openerp.tools as tools
44 from openerp.tools.translate import _
45 from openerp.tools import float_round, float_repr
46 import simplejson
47
48 _logger = logging.getLogger(__name__)
49
50 def _symbol_set(symb):
51     if symb == None or symb == False:
52         return None
53     elif isinstance(symb, unicode):
54         return symb.encode('utf-8')
55     return str(symb)
56
57
58 class _column(object):
59     """ Base of all fields, a database column
60
61         An instance of this object is a *description* of a database column. It will
62         not hold any data, but only provide the methods to manipulate data of an
63         ORM record or even prepare/update the database to hold such a field of data.
64     """
65     _classic_read = True
66     _classic_write = True
67     _prefetch = True
68     _properties = False
69     _type = 'unknown'
70     _obj = None
71     _multi = False
72     _symbol_c = '%s'
73     _symbol_f = _symbol_set
74     _symbol_set = (_symbol_c, _symbol_f)
75     _symbol_get = None
76
77     # used to hide a certain field type in the list of field types
78     _deprecated = False
79
80     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):
81         """
82
83         The 'manual' keyword argument specifies if the field is a custom one.
84         It corresponds to the 'state' column in ir_model_fields.
85
86         """
87         if domain is None:
88             domain = []
89         if context is None:
90             context = {}
91         self.states = states or {}
92         self.string = string
93         self.readonly = readonly
94         self.required = required
95         self.size = size
96         self.help = args.get('help', '')
97         self.priority = priority
98         self.change_default = change_default
99         self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
100         self.translate = translate
101         self._domain = domain
102         self._context = context
103         self.write = False
104         self.read = False
105         self.view_load = 0
106         self.select = select
107         self.manual = manual
108         self.selectable = True
109         self.group_operator = args.get('group_operator', False)
110         for a in args:
111             if args[a]:
112                 setattr(self, a, args[a])
113
114     def restart(self):
115         pass
116
117     def set(self, cr, obj, id, name, value, user=None, context=None):
118         cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
119
120     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
121         raise Exception(_('undefined get method !'))
122
123     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
124         ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
125         res = obj.read(cr, uid, ids, [name], context=context)
126         return [x[name] for x in res]
127
128
129 # ---------------------------------------------------------
130 # Simple fields
131 # ---------------------------------------------------------
132 class boolean(_column):
133     _type = 'boolean'
134     _symbol_c = '%s'
135     _symbol_f = lambda x: x and 'True' or 'False'
136     _symbol_set = (_symbol_c, _symbol_f)
137
138     def __init__(self, string='unknown', required=False, **args):
139         super(boolean, self).__init__(string=string, required=required, **args)
140         if required:
141             _logger.warning(
142                 "required=True is deprecated: making a boolean field"
143                 " `required` has no effect, as NULL values are "
144                 "automatically turned into False.")
145
146 class integer(_column):
147     _type = 'integer'
148     _symbol_c = '%s'
149     _symbol_f = lambda x: int(x or 0)
150     _symbol_set = (_symbol_c, _symbol_f)
151     _symbol_get = lambda self,x: x or 0
152
153     def __init__(self, string='unknown', required=False, **args):
154         super(integer, self).__init__(string=string, required=required, **args)
155         if required:
156             _logger.warning(
157                 "required=True is deprecated: making an integer field"
158                 " `required` has no effect, as NULL values are "
159                 "automatically turned into 0.")
160
161 class integer_big(_column):
162     """Experimental 64 bit integer column type, currently unused.
163
164        TODO: this field should work fine for values up
165              to 32 bits, but greater values will not fit
166              in the XML-RPC int type, so a specific
167              get() method is needed to pass them as floats,
168              like what we do for integer functional fields.
169     """
170     _type = 'integer_big'
171     # do not reference the _symbol_* of integer class, as that would possibly
172     # unbind the lambda functions
173     _symbol_c = '%s'
174     _symbol_f = lambda x: int(x or 0)
175     _symbol_set = (_symbol_c, _symbol_f)
176     _symbol_get = lambda self,x: x or 0
177     _deprecated = True
178
179     def __init__(self, string='unknown', required=False, **args):
180         super(integer_big, self).__init__(string=string, required=required, **args)
181         if required:
182             _logger.warning(
183                 "required=True is deprecated: making an integer_big field"
184                 " `required` has no effect, as NULL values are "
185                 "automatically turned into 0.")
186
187 class reference(_column):
188     _type = 'reference'
189     _classic_read = False # post-process to handle missing target
190
191     def __init__(self, string, selection, size, **args):
192         _column.__init__(self, string=string, size=size, selection=selection, **args)
193
194     def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
195         result = {}
196         # copy initial values fetched previously.
197         for value in values:
198             result[value['id']] = value[name]
199             if value[name]:
200                 model, res_id = value[name].split(',')
201                 if not obj.pool.get(model).exists(cr, uid, [int(res_id)], context=context):
202                     result[value['id']] = False
203         return result
204
205 class char(_column):
206     _type = 'char'
207
208     def __init__(self, string, size, **args):
209         _column.__init__(self, string=string, size=size, **args)
210         self._symbol_set = (self._symbol_c, self._symbol_set_char)
211
212     # takes a string (encoded in utf8) and returns a string (encoded in utf8)
213     def _symbol_set_char(self, symb):
214         #TODO:
215         # * we need to remove the "symb==False" from the next line BUT
216         #   for now too many things rely on this broken behavior
217         # * the symb==None test should be common to all data types
218         if symb == None or symb == False:
219             return None
220
221         # we need to convert the string to a unicode object to be able
222         # to evaluate its length (and possibly truncate it) reliably
223         u_symb = tools.ustr(symb)
224
225         return u_symb[:self.size].encode('utf8')
226
227
228 class text(_column):
229     _type = 'text'
230
231 import __builtin__
232
233 class float(_column):
234     _type = 'float'
235     _symbol_c = '%s'
236     _symbol_f = lambda x: __builtin__.float(x or 0.0)
237     _symbol_set = (_symbol_c, _symbol_f)
238     _symbol_get = lambda self,x: x or 0.0
239
240     def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
241         _column.__init__(self, string=string, required=required, **args)
242         self.digits = digits
243         # synopsis: digits_compute(cr) ->  (precision, scale)
244         self.digits_compute = digits_compute
245         if required:
246             _logger.warning(
247                 "required=True is deprecated: making a float field"
248                 " `required` has no effect, as NULL values are "
249                 "automatically turned into 0.0.")
250
251     def digits_change(self, cr):
252         if self.digits_compute:
253             self.digits = self.digits_compute(cr)
254         if self.digits:
255             precision, scale = self.digits
256             self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
257                                                                        precision_digits=scale),
258                                                            precision_digits=scale))
259
260 class date(_column):
261     _type = 'date'
262     @staticmethod
263     def today(*args):
264         """ Returns the current date in a format fit for being a
265         default value to a ``date`` field.
266
267         This method should be provided as is to the _defaults dict, it
268         should not be called.
269         """
270         return DT.date.today().strftime(
271             tools.DEFAULT_SERVER_DATE_FORMAT)
272
273 class datetime(_column):
274     _type = 'datetime'
275     @staticmethod
276     def now(*args):
277         """ Returns the current datetime in a format fit for being a
278         default value to a ``datetime`` field.
279
280         This method should be provided as is to the _defaults dict, it
281         should not be called.
282         """
283         return DT.datetime.now().strftime(
284             tools.DEFAULT_SERVER_DATETIME_FORMAT)
285
286 class time(_column):
287     _type = 'time'
288     _deprecated = True
289     @staticmethod
290     def now( *args):
291         """ Returns the current time in a format fit for being a
292         default value to a ``time`` field.
293
294         This method should be proivided as is to the _defaults dict,
295         it should not be called.
296         """
297         return DT.datetime.now().strftime(
298             tools.DEFAULT_SERVER_TIME_FORMAT)
299
300 class binary(_column):
301     _type = 'binary'
302     _symbol_c = '%s'
303     _symbol_f = lambda symb: symb and Binary(symb) or None
304     _symbol_set = (_symbol_c, _symbol_f)
305     _symbol_get = lambda self, x: x and str(x)
306
307     _classic_read = False
308     _prefetch = False
309
310     def __init__(self, string='unknown', filters=None, **args):
311         _column.__init__(self, string=string, **args)
312         self.filters = filters
313
314     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
315         if not context:
316             context = {}
317         if not values:
318             values = []
319         res = {}
320         for i in ids:
321             val = None
322             for v in values:
323                 if v['id'] == i:
324                     val = v[name]
325                     break
326
327             # If client is requesting only the size of the field, we return it instead
328             # of the content. Presumably a separate request will be done to read the actual
329             # content if it's needed at some point.
330             # TODO: after 6.0 we should consider returning a dict with size and content instead of
331             #       having an implicit convention for the value
332             if val and context.get('bin_size_%s' % name, context.get('bin_size')):
333                 res[i] = tools.human_size(long(val))
334             else:
335                 res[i] = val
336         return res
337
338 class selection(_column):
339     _type = 'selection'
340
341     def __init__(self, selection, string='unknown', **args):
342         _column.__init__(self, string=string, **args)
343         self.selection = selection
344
345 # ---------------------------------------------------------
346 # Relationals fields
347 # ---------------------------------------------------------
348
349 #
350 # Values: (0, 0,  { fields })    create
351 #         (1, ID, { fields })    update
352 #         (2, ID)                remove (delete)
353 #         (3, ID)                unlink one (target id or target of relation)
354 #         (4, ID)                link
355 #         (5)                    unlink all (only valid for one2many)
356 #
357 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
358 class one2one(_column):
359     _classic_read = False
360     _classic_write = True
361     _type = 'one2one'
362     _deprecated = True
363
364     def __init__(self, obj, string='unknown', **args):
365         _logger.warning("The one2one field is deprecated and doesn't work anymore.")
366         _column.__init__(self, string=string, **args)
367         self._obj = obj
368
369     def set(self, cr, obj_src, id, field, act, user=None, context=None):
370         if not context:
371             context = {}
372         obj = obj_src.pool.get(self._obj)
373         self._table = obj_src.pool.get(self._obj)._table
374         if act[0] == 0:
375             id_new = obj.create(cr, user, act[1])
376             cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
377         else:
378             cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
379             id = cr.fetchone()[0]
380             obj.write(cr, user, [id], act[1], context=context)
381
382     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
383         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
384
385
386 class many2one(_column):
387     _classic_read = False
388     _classic_write = True
389     _type = 'many2one'
390     _symbol_c = '%s'
391     _symbol_f = lambda x: x or None
392     _symbol_set = (_symbol_c, _symbol_f)
393
394     def __init__(self, obj, string='unknown', **args):
395         _column.__init__(self, string=string, **args)
396         self._obj = obj
397
398     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
399         if context is None:
400             context = {}
401         if values is None:
402             values = {}
403
404         res = {}
405         for r in values:
406             res[r['id']] = r[name]
407         for id in ids:
408             res.setdefault(id, '')
409         obj = obj.pool.get(self._obj)
410
411         # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
412         # we use uid=1 because the visibility of a many2one field value (just id and name)
413         # must be the access right of the parent form and not the linked object itself.
414         records = dict(obj.name_get(cr, 1,
415                                     list(set([x for x in res.values() if isinstance(x, (int,long))])),
416                                     context=context))
417         for id in res:
418             if res[id] in records:
419                 res[id] = (res[id], records[res[id]])
420             else:
421                 res[id] = False
422         return res
423
424     def set(self, cr, obj_src, id, field, values, user=None, context=None):
425         if not context:
426             context = {}
427         obj = obj_src.pool.get(self._obj)
428         self._table = obj_src.pool.get(self._obj)._table
429         if type(values) == type([]):
430             for act in values:
431                 if act[0] == 0:
432                     id_new = obj.create(cr, act[2])
433                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
434                 elif act[0] == 1:
435                     obj.write(cr, [act[1]], act[2], context=context)
436                 elif act[0] == 2:
437                     cr.execute('delete from '+self._table+' where id=%s', (act[1],))
438                 elif act[0] == 3 or act[0] == 5:
439                     cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
440                 elif act[0] == 4:
441                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
442         else:
443             if values:
444                 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
445             else:
446                 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
447
448     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
449         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
450
451
452 class one2many(_column):
453     _classic_read = False
454     _classic_write = False
455     _prefetch = False
456     _type = 'one2many'
457
458     def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
459         _column.__init__(self, string=string, **args)
460         self._obj = obj
461         self._fields_id = fields_id
462         self._limit = limit
463         #one2many can't be used as condition for defaults
464         assert(self.change_default != True)
465
466     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
467         if context is None:
468             context = {}
469         if self._context:
470             context = context.copy()
471         context.update(self._context)
472         if values is None:
473             values = {}
474
475         res = {}
476         for id in ids:
477             res[id] = []
478
479         ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
480         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
481             if r[self._fields_id] in res:
482                 res[r[self._fields_id]].append(r['id'])
483         return res
484
485     def set(self, cr, obj, id, field, values, user=None, context=None):
486         result = []
487         if not context:
488             context = {}
489         if self._context:
490             context = context.copy()
491         context.update(self._context)
492         context['no_store_function'] = True
493         if not values:
494             return
495         _table = obj.pool.get(self._obj)._table
496         obj = obj.pool.get(self._obj)
497         for act in values:
498             if act[0] == 0:
499                 act[2][self._fields_id] = id
500                 id_new = obj.create(cr, user, act[2], context=context)
501                 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
502             elif act[0] == 1:
503                 obj.write(cr, user, [act[1]], act[2], context=context)
504             elif act[0] == 2:
505                 obj.unlink(cr, user, [act[1]], context=context)
506             elif act[0] == 3:
507                 reverse_rel = obj._all_columns.get(self._fields_id)
508                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
509                 # if the model has on delete cascade, just delete the row
510                 if reverse_rel.column.ondelete == "cascade":
511                     obj.unlink(cr, user, [act[1]], context=context)
512                 else:
513                     cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
514             elif act[0] == 4:
515                 # Must use write() to recompute parent_store structure if needed
516                 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
517             elif act[0] == 5:
518                 reverse_rel = obj._all_columns.get(self._fields_id)
519                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
520                 # if the o2m has a static domain we must respect it when unlinking
521                 extra_domain = self._domain if isinstance(getattr(self, '_domain', None), list) else [] 
522                 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
523                 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
524                 # otherwise we only nullify the reverse foreign key column.
525                 if reverse_rel.column.ondelete == "cascade":
526                     obj.unlink(cr, user, ids_to_unlink, context=context)
527                 else:
528                     obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
529             elif act[0] == 6:
530                 # Must use write() to recompute parent_store structure if needed
531                 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
532                 ids2 = act[2] or [0]
533                 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
534                 ids3 = map(lambda x:x[0], cr.fetchall())
535                 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
536         return result
537
538     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
539         return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
540
541
542 #
543 # Values: (0, 0,  { fields })    create
544 #         (1, ID, { fields })    update (write fields to ID)
545 #         (2, ID)                remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
546 #         (3, ID)                unlink (delete the relationship between the two objects but does not delete ID)
547 #         (4, ID)                link (add a relationship)
548 #         (5, ID)                unlink all
549 #         (6, ?, ids)            set a list of links
550 #
551 class many2many(_column):
552     """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
553        low-level details of the intermediary relationship table transparently.
554        A many-to-many relationship is always symmetrical, and can be declared and accessed
555        from either endpoint model.
556        If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
557        or id2 (destination foreign key column name) are not specified, the system will
558        provide default values. This will by default only allow one single symmetrical
559        many-to-many relationship between the source and destination model.
560        For multiple many-to-many relationship between the same models and for
561        relationships where source and destination models are the same, ``rel``, ``id1``
562        and ``id2`` should be specified explicitly.
563
564        :param str obj: destination model
565        :param str rel: optional name of the intermediary relationship table. If not specified,
566                        a canonical name will be derived based on the alphabetically-ordered
567                        model names of the source and destination (in the form: ``amodel_bmodel_rel``).
568                        Automatic naming is not possible when the source and destination are
569                        the same, for obvious ambiguity reasons.
570        :param str id1: optional name for the column holding the foreign key to the current
571                        model in the relationship table. If not specified, a canonical name
572                        will be derived based on the model name (in the form: `src_model_id`).
573        :param str id2: optional name for the column holding the foreign key to the destination
574                        model in the relationship table. If not specified, a canonical name
575                        will be derived based on the model name (in the form: `dest_model_id`)
576        :param str string: field label
577     """
578     _classic_read = False
579     _classic_write = False
580     _prefetch = False
581     _type = 'many2many'
582
583     def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
584         """
585         """
586         _column.__init__(self, string=string, **args)
587         self._obj = obj
588         if rel and '.' 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 _sql_names(self, source_model):
597         """Return the SQL names defining the structure of the m2m relationship table
598
599             :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
600                      local_col is the name of the column holding the current model's FK, and
601                      dest_col is the name of the column holding the destination model's FK, and
602         """
603         tbl, col1, col2 = self._rel, self._id1, self._id2
604         if not all((tbl, col1, col2)):
605             # the default table name is based on the stable alphabetical order of tables
606             dest_model = source_model.pool.get(self._obj)
607             tables = tuple(sorted([source_model._table, dest_model._table]))
608             if not tbl:
609                 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
610                                                'is not possible when source and destination models are '\
611                                                'the same'
612                 tbl = '%s_%s_rel' % tables
613             if not col1:
614                 col1 = '%s_id' % source_model._table
615             if not col2:
616                 col2 = '%s_id' % dest_model._table
617         return (tbl, col1, col2)
618
619     def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
620         if not context:
621             context = {}
622         if not values:
623             values = {}
624         res = {}
625         if not ids:
626             return res
627         for id in ids:
628             res[id] = []
629         if offset:
630             _logger.warning(
631                 "Specifying offset at a many2many.get() is deprecated and may"
632                 " produce unpredictable results.")
633         obj = model.pool.get(self._obj)
634         rel, id1, id2 = self._sql_names(model)
635
636         # static domains are lists, and are evaluated both here and on client-side, while string
637         # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
638         # FIXME: make this distinction explicit in API!
639         domain = isinstance(self._domain, list) and self._domain or []
640
641         wquery = obj._where_calc(cr, user, domain, context=context)
642         obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
643         from_c, where_c, where_params = wquery.get_sql()
644         if where_c:
645             where_c = ' AND ' + where_c
646
647         if offset or self._limit:
648             order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
649         else:
650             order_by = ''
651
652         limit_str = ''
653         if self._limit is not None:
654             limit_str = ' LIMIT %d' % self._limit
655
656         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
657                    FROM %(rel)s, %(from_c)s \
658                   WHERE %(rel)s.%(id1)s IN %%s \
659                     AND %(rel)s.%(id2)s = %(tbl)s.id \
660                  %(where_c)s  \
661                  %(order_by)s \
662                  %(limit)s \
663                  OFFSET %(offset)d' \
664             % {'rel': rel,
665                'from_c': from_c,
666                'tbl': obj._table,
667                'id1': id1,
668                'id2': id2,
669                'where_c': where_c,
670                'limit': limit_str,
671                'order_by': order_by,
672                'offset': offset,
673               }
674         cr.execute(query, [tuple(ids),] + where_params)
675         for r in cr.fetchall():
676             res[r[1]].append(r[0])
677         return res
678
679     def set(self, cr, model, id, name, values, user=None, context=None):
680         if not context:
681             context = {}
682         if not values:
683             return
684         rel, id1, id2 = self._sql_names(model)
685         obj = model.pool.get(self._obj)
686         for act in values:
687             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
688                 continue
689             if act[0] == 0:
690                 idnew = obj.create(cr, user, act[2], context=context)
691                 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
692             elif act[0] == 1:
693                 obj.write(cr, user, [act[1]], act[2], context=context)
694             elif act[0] == 2:
695                 obj.unlink(cr, user, [act[1]], context=context)
696             elif act[0] == 3:
697                 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
698             elif act[0] == 4:
699                 # following queries are in the same transaction - so should be relatively safe
700                 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
701                 if not cr.fetchone():
702                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
703             elif act[0] == 5:
704                 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
705             elif act[0] == 6:
706
707                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
708                 if d1:
709                     d1 = ' and ' + ' and '.join(d1)
710                 else:
711                     d1 = ''
712                 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)
713
714                 for act_nbr in act[2]:
715                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
716
717     #
718     # TODO: use a name_search
719     #
720     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
721         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
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 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 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 model: model to which the field belongs (should be ``self`` for
891                           a model method)
892         :param orm 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 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._type == 'float':
1017             if self.digits_compute:
1018                 self.digits = self.digits_compute(cr)
1019             if self.digits:
1020                 precision, scale = self.digits
1021                 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1022                                                                            precision_digits=scale),
1023                                                                precision_digits=scale))
1024
1025     def search(self, cr, uid, obj, name, args, context=None):
1026         if not self._fnct_search:
1027             #CHECKME: should raise an exception
1028             return []
1029         return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1030
1031     def postprocess(self, cr, uid, obj, field, value=None, context=None):
1032         if context is None:
1033             context = {}
1034         result = value
1035         field_type = obj._columns[field]._type
1036         if field_type == "many2one":
1037             # make the result a tuple if it is not already one
1038             if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1039                 obj_model = obj.pool.get(obj._columns[field].relation)
1040                 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1041                 result = (value, dict_names[value])
1042
1043         if field_type == 'binary':
1044             if context.get('bin_size'):
1045                 # client requests only the size of binary fields
1046                 result = get_nice_size(value)
1047             elif not context.get('bin_raw'):
1048                 result = sanitize_binary_value(value)
1049
1050         if field_type in ("integer","integer_big") and value > xmlrpclib.MAXINT:
1051             # integer/long values greater than 2^31-1 are not supported
1052             # in pure XMLRPC, so we have to pass them as floats :-(
1053             # This is not needed for stored fields and non-functional integer
1054             # fields, as their values are constrained by the database backend
1055             # to the same 32bits signed int limit.
1056             result = float(value)
1057         return result
1058
1059     def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1060         result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1061         for id in ids:
1062             if self._multi and id in result:
1063                 for field, value in result[id].iteritems():
1064                     if value:
1065                         result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1066             elif result.get(id):
1067                 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1068         return result
1069
1070     def set(self, cr, obj, id, name, value, user=None, context=None):
1071         if not context:
1072             context = {}
1073         if self._fnct_inv:
1074             self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
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('foo_id', 'frol', 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         result = []
1196         obj_name = obj._name
1197         for i in range(len(self._arg)):
1198             f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1199             result.append({
1200                 'object': obj_name,
1201                 'type': f['type']
1202
1203             })
1204             if f.get('relation',False):
1205                 obj_name = f['relation']
1206                 result[-1]['relation'] = f['relation']
1207         self._relations = result
1208
1209
1210 class sparse(function):   
1211
1212     def convert_value(self, obj, cr, uid, record, value, read_value, context=None):        
1213         """
1214             + For a many2many field, a list of tuples is expected.
1215               Here is the list of tuple that are accepted, with the corresponding semantics ::
1216
1217                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
1218                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
1219                  (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
1220                  (3, ID)                cut the link to the linked record with id = ID (delete the relationship between the two objects but does not delete the target object itself)
1221                  (4, ID)                link to existing record with id = ID (adds a relationship)
1222                  (5)                    unlink all (like using (3,ID) for all linked records)
1223                  (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1224
1225                  Example:
1226                     [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1227
1228             + For a one2many field, a lits of tuples is expected.
1229               Here is the list of tuple that are accepted, with the corresponding semantics ::
1230
1231                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
1232                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
1233                  (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
1234
1235                  Example:
1236                     [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1237         """
1238
1239         if self._type == 'many2many':
1240             assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1241             return value[0][2]
1242
1243         elif self._type == 'one2many':
1244             if not read_value:
1245                 read_value = []
1246             relation_obj = obj.pool.get(self.relation)
1247             for vals in value:
1248                 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1249                 if vals[0] == 0:
1250                     read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1251                 elif vals[0] == 1:
1252                     relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1253                 elif vals[0] == 2:
1254                     relation_obj.unlink(cr, uid, vals[1], context=context)
1255                     read_value.remove(vals[1])
1256             return read_value
1257         return value
1258
1259
1260     def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1261         if not type(ids) == list:
1262             ids = [ids]
1263         records = obj.browse(cr, uid, ids, context=context)
1264         for record in records:
1265             # grab serialized value as object - already deserialized
1266             serialized = getattr(record, self.serialization_field)
1267             if value is None:
1268                 # simply delete the key to unset it.
1269                 serialized.pop(field_name, None)
1270             else: 
1271                 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1272             obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1273         return True
1274
1275     def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1276         results = {}
1277         records = obj.browse(cr, uid, ids, context=context)
1278         for record in records:
1279             # grab serialized value as object - already deserialized
1280             serialized = getattr(record, self.serialization_field)
1281             results[record.id] = {}
1282             for field_name in field_names:
1283                 field_type = obj._columns[field_name]._type
1284                 value = serialized.get(field_name, False)
1285                 if field_type in ('one2many','many2many'):
1286                     value = value or []
1287                     if value:
1288                         # filter out deleted records as superuser
1289                         relation_obj = obj.pool.get(self.relation)
1290                         value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1291                 if type(value) in (int,long) and field_type == 'many2one':
1292                     relation_obj = obj.pool.get(self.relation)
1293                     # check for deleted record as superuser
1294                     if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1295                         value = False
1296                 results[record.id][field_name] = value
1297         return results
1298
1299     def __init__(self, serialization_field, **kwargs):
1300         self.serialization_field = serialization_field
1301         return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1302      
1303
1304
1305 # ---------------------------------------------------------
1306 # Dummy fields
1307 # ---------------------------------------------------------
1308
1309 class dummy(function):
1310     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1311         return []
1312
1313     def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1314         return False
1315
1316     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1317         return {}
1318
1319     def __init__(self, *arg, **args):
1320         self.arg = arg
1321         self._relations = []
1322         super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1323
1324 # ---------------------------------------------------------
1325 # Serialized fields
1326 # ---------------------------------------------------------
1327
1328 class serialized(_column):
1329     """ A field able to store an arbitrary python data structure.
1330     
1331         Note: only plain components allowed.
1332     """
1333     
1334     def _symbol_set_struct(val):
1335         return simplejson.dumps(val)
1336
1337     def _symbol_get_struct(self, val):
1338         return simplejson.loads(val or '{}')
1339     
1340     _prefetch = False
1341     _type = 'serialized'
1342
1343     _symbol_c = '%s'
1344     _symbol_f = _symbol_set_struct
1345     _symbol_set = (_symbol_c, _symbol_f)
1346     _symbol_get = _symbol_get_struct
1347
1348 # TODO: review completly this class for speed improvement
1349 class property(function):
1350
1351     def _get_default(self, obj, cr, uid, prop_name, context=None):
1352         return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1353
1354     def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1355         """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1356
1357            :param list of string prop_names: list of name of property fields for those we want the default value
1358            :return: map of property field names to their default value
1359            :rtype: dict
1360         """
1361         prop = obj.pool.get('ir.property')
1362         res = {}
1363         for prop_name in prop_names:
1364             res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1365         return res
1366
1367     def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1368         prop = obj.pool.get('ir.property')
1369         vids = [obj._name + ',' + str(oid) for oid in  ids]
1370
1371         domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1372         #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1373         if vids:
1374             domain = [('res_id', 'in', vids)] + domain
1375         return prop.search(cr, uid, domain, context=context)
1376
1377     # TODO: to rewrite more clean
1378     def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1379         if context is None:
1380             context = {}
1381
1382         nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1383         if nids:
1384             cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1385
1386         default_val = self._get_default(obj, cr, uid, prop_name, context)
1387
1388         property_create = False
1389         if isinstance(default_val, openerp.osv.orm.browse_record):
1390             if default_val.id != id_val:
1391                 property_create = True
1392         elif default_val != id_val:
1393             property_create = True
1394
1395         if property_create:
1396             def_id = self._field_get(cr, uid, obj._name, prop_name)
1397             company = obj.pool.get('res.company')
1398             cid = company._company_default_get(cr, uid, obj._name, def_id,
1399                                                context=context)
1400             propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1401                                                              context=context)
1402             prop = obj.pool.get('ir.property')
1403             return prop.create(cr, uid, {
1404                 'name': propdef.name,
1405                 'value': id_val,
1406                 'res_id': obj._name+','+str(id),
1407                 'company_id': cid,
1408                 'fields_id': def_id,
1409                 'type': self._type,
1410             }, context=context)
1411         return False
1412
1413     def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1414         prop = obj.pool.get('ir.property')
1415         # get the default values (for res_id = False) for the property fields
1416         default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1417
1418         # build the dictionary that will be returned
1419         res = {}
1420         for id in ids:
1421             res[id] = default_val.copy()
1422
1423         for prop_name in prop_names:
1424             property_field = obj._all_columns.get(prop_name).column
1425             property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1426             # If the property field is a m2o field, we will append the id of the value to name_get_ids
1427             # in order to make a name_get in batch for all the ids needed.
1428             name_get_ids = {}
1429             for id in ids:
1430                 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1431                 obj_reference = obj._name + ',' + str(id)
1432                 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1433                 if value:
1434                     res[id][prop_name] = value
1435                 # Check existence as root (as seeing the name of a related
1436                 # object depends on access right of source document,
1437                 # not target, so user may not have access) in order to avoid
1438                 # pointing on an unexisting record.
1439                 if property_destination_obj:
1440                     if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1441                         name_get_ids[id] = res[id][prop_name].id
1442                     else:
1443                         res[id][prop_name] = False
1444             if property_destination_obj:
1445                 # name_get as root (as seeing the name of a related
1446                 # object depends on access right of source document,
1447                 # not target, so user may not have access.)
1448                 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1449                 # the property field is a m2o, we need to return a tuple with (id, name)
1450                 for k, v in name_get_ids.iteritems():
1451                     if res[k][prop_name]:
1452                         res[k][prop_name] = (v , name_get_values.get(v))
1453         return res
1454
1455     def _field_get(self, cr, uid, model_name, prop):
1456         if not self.field_id.get(cr.dbname):
1457             cr.execute('SELECT id \
1458                     FROM ir_model_fields \
1459                     WHERE name=%s AND model=%s', (prop, model_name))
1460             res = cr.fetchone()
1461             self.field_id[cr.dbname] = res and res[0]
1462         return self.field_id[cr.dbname]
1463
1464     def __init__(self, obj_prop, **args):
1465         # TODO remove obj_prop parameter (use many2one type)
1466         self.field_id = {}
1467         function.__init__(self, self._fnct_read, False, self._fnct_write,
1468                           obj_prop, multi='properties', **args)
1469
1470     def restart(self):
1471         self.field_id = {}
1472
1473
1474 def field_to_dict(model, cr, user, field, context=None):
1475     """ Return a dictionary representation of a field.
1476
1477     The string, help, and selection attributes (if any) are untranslated.  This
1478     representation is the one returned by fields_get() (fields_get() will do
1479     the translation).
1480
1481     """
1482
1483     res = {'type': field._type}
1484     # This additional attributes for M2M and function field is added
1485     # because we need to display tooltip with this additional information
1486     # when client is started in debug mode.
1487     if isinstance(field, function):
1488         res['function'] = field._fnct and field._fnct.func_name or False
1489         res['store'] = field.store
1490         if isinstance(field.store, dict):
1491             res['store'] = str(field.store)
1492         res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1493         res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1494         res['fnct_inv_arg'] = field._fnct_inv_arg or False
1495         res['func_obj'] = field._obj or False
1496     if isinstance(field, many2many):
1497         (table, col1, col2) = field._sql_names(model)
1498         res['related_columns'] = [col1, col2]
1499         res['third_table'] = table
1500     for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1501             'change_default', 'translate', 'help', 'select', 'selectable'):
1502         if getattr(field, arg):
1503             res[arg] = getattr(field, arg)
1504     for arg in ('digits', 'invisible', 'filters'):
1505         if getattr(field, arg, None):
1506             res[arg] = getattr(field, arg)
1507
1508     if field.string:
1509         res['string'] = field.string
1510     if field.help:
1511         res['help'] = field.help
1512
1513     if hasattr(field, 'selection'):
1514         if isinstance(field.selection, (tuple, list)):
1515             res['selection'] = field.selection
1516         else:
1517             # call the 'dynamic selection' function
1518             res['selection'] = field.selection(model, cr, user, context)
1519     if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1520         res['relation'] = field._obj
1521         res['domain'] = field._domain
1522         res['context'] = field._context
1523
1524     if isinstance(field, one2many):
1525         res['relation_field'] = field._fields_id
1526
1527     return res
1528
1529
1530 class column_info(object):
1531     """Struct containing details about an osv column, either one local to
1532        its model, or one inherited via _inherits.
1533
1534        :attr name: name of the column
1535        :attr column: column instance, subclass of osv.fields._column
1536        :attr parent_model: if the column is inherited, name of the model
1537                            that contains it, None for local columns.
1538        :attr parent_column: the name of the column containing the m2o
1539                             relationship to the parent model that contains
1540                             this column, None for local columns.
1541        :attr original_parent: if the column is inherited, name of the original
1542                             parent model that contains it i.e in case of multilevel
1543                             inheritence, None for local columns.
1544     """
1545     def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1546         self.name = name
1547         self.column = column
1548         self.parent_model = parent_model
1549         self.parent_column = parent_column
1550         self.original_parent = original_parent
1551
1552 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1553