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