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