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