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