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