61c18498baf4900e27298a4b0e84fce45a3727be
[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.groups 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             ('_origin', 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         res = dict((id, []) for id in ids)
675
676         comodel = obj.pool[self._obj].browse(cr, user, [], context)
677         inverse = self._fields_id
678         domain = self._domain(obj) if callable(self._domain) else self._domain
679         domain = domain + [(inverse, 'in', ids)]
680
681         for record in comodel.search(domain, limit=self._limit):
682             # Note: record[inverse] can be a record or an integer!
683             assert int(record[inverse]) in res
684             res[int(record[inverse])].append(record.id)
685
686         return res
687
688     def set(self, cr, obj, id, field, values, user=None, context=None):
689         result = []
690         context = dict(context or {})
691         context.update(self._context)
692         context['recompute'] = False    # recomputation is done by outer create/write
693         if not values:
694             return
695         obj = obj.pool[self._obj]
696         _table = obj._table
697         for act in values:
698             if act[0] == 0:
699                 act[2][self._fields_id] = id
700                 id_new = obj.create(cr, user, act[2], context=context)
701                 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
702             elif act[0] == 1:
703                 obj.write(cr, user, [act[1]], act[2], context=context)
704             elif act[0] == 2:
705                 obj.unlink(cr, user, [act[1]], context=context)
706             elif act[0] == 3:
707                 reverse_rel = obj._all_columns.get(self._fields_id)
708                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
709                 # if the model has on delete cascade, just delete the row
710                 if reverse_rel.column.ondelete == "cascade":
711                     obj.unlink(cr, user, [act[1]], context=context)
712                 else:
713                     cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
714             elif act[0] == 4:
715                 # table of the field (parent_model in case of inherit)
716                 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
717                 field_table = obj.pool[field_model]._table
718                 cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
719                 if not cr.fetchone():
720                     # Must use write() to recompute parent_store structure if needed and check access rules
721                     obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
722             elif act[0] == 5:
723                 reverse_rel = obj._all_columns.get(self._fields_id)
724                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
725                 # if the o2m has a static domain we must respect it when unlinking
726                 domain = self._domain(obj) if callable(self._domain) else self._domain
727                 extra_domain = domain or []
728                 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
729                 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
730                 # otherwise we only nullify the reverse foreign key column.
731                 if reverse_rel.column.ondelete == "cascade":
732                     obj.unlink(cr, user, ids_to_unlink, context=context)
733                 else:
734                     obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
735             elif act[0] == 6:
736                 # Must use write() to recompute parent_store structure if needed
737                 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
738                 ids2 = act[2] or [0]
739                 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
740                 ids3 = map(lambda x:x[0], cr.fetchall())
741                 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
742         return result
743
744     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
745         domain = self._domain(obj) if callable(self._domain) else self._domain
746         return obj.pool[self._obj].name_search(cr, uid, value, domain, operator, context=context,limit=limit)
747
748     @classmethod
749     def _as_display_name(cls, field, cr, uid, obj, value, context=None):
750         raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)') 
751
752 #
753 # Values: (0, 0,  { fields })    create
754 #         (1, ID, { fields })    update (write fields to ID)
755 #         (2, ID)                remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
756 #         (3, ID)                unlink (delete the relationship between the two objects but does not delete ID)
757 #         (4, ID)                link (add a relationship)
758 #         (5, ID)                unlink all
759 #         (6, ?, ids)            set a list of links
760 #
761 class many2many(_column):
762     """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
763        low-level details of the intermediary relationship table transparently.
764        A many-to-many relationship is always symmetrical, and can be declared and accessed
765        from either endpoint model.
766        If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
767        or id2 (destination foreign key column name) are not specified, the system will
768        provide default values. This will by default only allow one single symmetrical
769        many-to-many relationship between the source and destination model.
770        For multiple many-to-many relationship between the same models and for
771        relationships where source and destination models are the same, ``rel``, ``id1``
772        and ``id2`` should be specified explicitly.
773
774        :param str obj: destination model
775        :param str rel: optional name of the intermediary relationship table. If not specified,
776                        a canonical name will be derived based on the alphabetically-ordered
777                        model names of the source and destination (in the form: ``amodel_bmodel_rel``).
778                        Automatic naming is not possible when the source and destination are
779                        the same, for obvious ambiguity reasons.
780        :param str id1: optional name for the column holding the foreign key to the current
781                        model in the relationship table. If not specified, a canonical name
782                        will be derived based on the model name (in the form: `src_model_id`).
783        :param str id2: optional name for the column holding the foreign key to the destination
784                        model in the relationship table. If not specified, a canonical name
785                        will be derived based on the model name (in the form: `dest_model_id`)
786        :param str string: field label
787     """
788     _classic_read = False
789     _classic_write = False
790     _prefetch = False
791     _type = 'many2many'
792
793     def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
794         """
795         """
796         _column.__init__(self, string=string, **args)
797         self._obj = obj
798         if rel and '.' in rel:
799             raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
800                 'You used %s, which is not a valid SQL table name.')% (string,rel))
801         self._rel = rel
802         self._id1 = id1
803         self._id2 = id2
804         self._limit = limit
805
806     def to_field_args(self):
807         args = super(many2many, self).to_field_args()
808         args['comodel_name'] = self._obj
809         args['relation'] = self._rel
810         args['column1'] = self._id1
811         args['column2'] = self._id2
812         args['limit'] = self._limit
813         return args
814
815     def _sql_names(self, source_model):
816         """Return the SQL names defining the structure of the m2m relationship table
817
818             :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
819                      local_col is the name of the column holding the current model's FK, and
820                      dest_col is the name of the column holding the destination model's FK, and
821         """
822         tbl, col1, col2 = self._rel, self._id1, self._id2
823         if not all((tbl, col1, col2)):
824             # the default table name is based on the stable alphabetical order of tables
825             dest_model = source_model.pool[self._obj]
826             tables = tuple(sorted([source_model._table, dest_model._table]))
827             if not tbl:
828                 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
829                                                'is not possible when source and destination models are '\
830                                                'the same'
831                 tbl = '%s_%s_rel' % tables
832             if not col1:
833                 col1 = '%s_id' % source_model._table
834             if not col2:
835                 col2 = '%s_id' % dest_model._table
836         return tbl, col1, col2
837
838     def _get_query_and_where_params(self, cr, model, ids, values, where_params):
839         """ Extracted from ``get`` to facilitate fine-tuning of the generated
840             query. """
841         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
842                    FROM %(rel)s, %(from_c)s \
843                   WHERE %(rel)s.%(id1)s IN %%s \
844                     AND %(rel)s.%(id2)s = %(tbl)s.id \
845                  %(where_c)s  \
846                  %(order_by)s \
847                  %(limit)s \
848                  OFFSET %(offset)d' \
849                  % values
850         return query, where_params
851
852     def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
853         if not context:
854             context = {}
855         if not values:
856             values = {}
857         res = {}
858         if not ids:
859             return res
860         for id in ids:
861             res[id] = []
862         if offset:
863             _logger.warning(
864                 "Specifying offset at a many2many.get() is deprecated and may"
865                 " produce unpredictable results.")
866         obj = model.pool[self._obj]
867         rel, id1, id2 = self._sql_names(model)
868
869         # static domains are lists, and are evaluated both here and on client-side, while string
870         # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
871         # FIXME: make this distinction explicit in API!
872         domain = isinstance(self._domain, list) and self._domain or []
873
874         wquery = obj._where_calc(cr, user, domain, context=context)
875         obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
876         from_c, where_c, where_params = wquery.get_sql()
877         if where_c:
878             where_c = ' AND ' + where_c
879
880         order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
881
882         limit_str = ''
883         if self._limit is not None:
884             limit_str = ' LIMIT %d' % self._limit
885
886         query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel,
887                'from_c': from_c,
888                'tbl': obj._table,
889                'id1': id1,
890                'id2': id2,
891                'where_c': where_c,
892                'limit': limit_str,
893                'order_by': order_by,
894                'offset': offset,
895                 }, where_params)
896
897         cr.execute(query, [tuple(ids),] + where_params)
898         for r in cr.fetchall():
899             res[r[1]].append(r[0])
900         return res
901
902     def set(self, cr, model, id, name, values, user=None, context=None):
903         if not context:
904             context = {}
905         if not values:
906             return
907         rel, id1, id2 = self._sql_names(model)
908         obj = model.pool[self._obj]
909         for act in values:
910             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
911                 continue
912             if act[0] == 0:
913                 idnew = obj.create(cr, user, act[2], context=context)
914                 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
915             elif act[0] == 1:
916                 obj.write(cr, user, [act[1]], act[2], context=context)
917             elif act[0] == 2:
918                 obj.unlink(cr, user, [act[1]], context=context)
919             elif act[0] == 3:
920                 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
921             elif act[0] == 4:
922                 # following queries are in the same transaction - so should be relatively safe
923                 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
924                 if not cr.fetchone():
925                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
926             elif act[0] == 5:
927                 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
928             elif act[0] == 6:
929
930                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
931                 if d1:
932                     d1 = ' and ' + ' and '.join(d1)
933                 else:
934                     d1 = ''
935                 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)
936
937                 for act_nbr in act[2]:
938                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
939
940     #
941     # TODO: use a name_search
942     #
943     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
944         return obj.pool[self._obj].search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
945
946     @classmethod
947     def _as_display_name(cls, field, cr, uid, obj, value, context=None):
948         raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)') 
949
950
951 def get_nice_size(value):
952     size = 0
953     if isinstance(value, (int,long)):
954         size = value
955     elif value: # this is supposed to be a string
956         size = len(value)
957     return tools.human_size(size)
958
959 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
960 # and http://bugs.python.org/issue10066
961 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
962
963 def sanitize_binary_value(value):
964     # binary fields should be 7-bit ASCII base64-encoded data,
965     # but we do additional sanity checks to make sure the values
966     # are not something else that won't pass via XML-RPC
967     if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
968         # these builtin types are meant to pass untouched
969         return value
970
971     # Handle invalid bytes values that will cause problems
972     # for XML-RPC. See for more info:
973     #  - http://bugs.python.org/issue10066
974     #  - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
975
976     # Coercing to unicode would normally allow it to properly pass via
977     # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
978     # (this works for _any_ byte values, thanks to the fallback
979     #  to latin-1 passthrough encoding when decoding to unicode)
980     value = tools.ustr(value)
981
982     # Due to Python bug #10066 this could still yield invalid XML
983     # bytes, specifically in the low byte range, that will crash
984     # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
985     # So check for low bytes values, and if any, perform
986     # base64 encoding - not very smart or useful, but this is
987     # our last resort to avoid crashing the request.
988     if invalid_xml_low_bytes.search(value):
989         # b64-encode after restoring the pure bytes with latin-1
990         # passthrough encoding
991         value = base64.b64encode(value.encode('latin-1'))
992
993     return value
994
995
996 # ---------------------------------------------------------
997 # Function fields
998 # ---------------------------------------------------------
999 class function(_column):
1000     """
1001     A field whose value is computed by a function (rather
1002     than being read from the database).
1003
1004     :param fnct: the callable that will compute the field value.
1005     :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
1006     :param fnct_inv: the callable that will allow writing values in that field
1007                      (if not provided, the field is read-only).
1008     :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
1009                          writing a value.
1010     :param str type: type of the field simulated by the function field
1011     :param fnct_search: the callable that allows searching on the field
1012                         (if not provided, search will not return any result).
1013     :param store: store computed value in database
1014                   (see :ref:`The *store* parameter <field-function-store>`).
1015     :type store: True or dict specifying triggers for field computation
1016     :param multi: name of batch for batch computation of function fields.
1017                   All fields with the same batch name will be computed by
1018                   a single function call. This changes the signature of the
1019                   ``fnct`` callable.
1020
1021     .. _field-function-fnct: The ``fnct`` parameter
1022
1023     .. rubric:: The ``fnct`` parameter
1024
1025     The callable implementing the function field must have the following signature:
1026
1027     .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
1028
1029         Implements the function field.
1030
1031         :param orm model: model to which the field belongs (should be ``self`` for
1032                           a model method)
1033         :param field_name(s): name of the field to compute, or if ``multi`` is provided,
1034                               list of field names to compute.
1035         :type field_name(s): str | [str]
1036         :param arg: arbitrary value passed when declaring the function field
1037         :rtype: dict
1038         :return: mapping of ``ids`` to computed values, or if multi is provided,
1039                  to a map of field_names to computed values
1040
1041     The values in the returned dictionary must be of the type specified by the type
1042     argument in the field declaration.
1043
1044     Here is an example with a simple function ``char`` function field::
1045
1046         # declarations
1047         def compute(self, cr, uid, ids, field_name, arg, context):
1048             result = {}
1049             # ...
1050             return result
1051         _columns['my_char'] = fields.function(compute, type='char', size=50)
1052
1053         # when called with ``ids=[1,2,3]``, ``compute`` could return:
1054         {
1055             1: 'foo',
1056             2: 'bar',
1057             3: False # null values should be returned explicitly too
1058         }
1059
1060     If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
1061     of the field names that should be computed. Each value in the returned
1062     dictionary must then be a dictionary mapping field names to values.
1063
1064     Here is an example where two function fields (``name`` and ``age``)
1065     are both computed by a single function field::
1066
1067         # declarations
1068         def compute(self, cr, uid, ids, field_names, arg, context):
1069             result = {}
1070             # ...
1071             return result
1072         _columns['name'] = fields.function(compute_person_data, type='char',\
1073                                            size=50, multi='person_data')
1074         _columns[''age'] = fields.function(compute_person_data, type='integer',\
1075                                            multi='person_data')
1076
1077         # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
1078         {
1079             1: {'name': 'Bob', 'age': 23},
1080             2: {'name': 'Sally', 'age': 19},
1081             3: {'name': 'unknown', 'age': False}
1082         }
1083
1084     .. _field-function-fnct-inv:
1085
1086     .. rubric:: The ``fnct_inv`` parameter
1087
1088     This callable implements the write operation for the function field
1089     and must have the following signature:
1090
1091     .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
1092
1093         Callable that implements the ``write`` operation for the function field.
1094
1095         :param orm model: model to which the field belongs (should be ``self`` for
1096                           a model method)
1097         :param int id: the identifier of the object to write on
1098         :param str field_name: name of the field to set
1099         :param fnct_inv_arg: arbitrary value passed when declaring the function field
1100         :return: True
1101
1102     When writing values for a function field, the ``multi`` parameter is ignored.
1103
1104     .. _field-function-fnct-search:
1105
1106     .. rubric:: The ``fnct_search`` parameter
1107
1108     This callable implements the search operation for the function field
1109     and must have the following signature:
1110
1111     .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
1112
1113         Callable that implements the ``search`` operation for the function field by expanding
1114         a search criterion based on the function field into a new domain based only on
1115         columns that are stored in the database.
1116
1117         :param orm model: model to which the field belongs (should be ``self`` for
1118                           a model method)
1119         :param orm model_again: same value as ``model`` (seriously! this is for backwards
1120                                 compatibility)
1121         :param str field_name: name of the field to search on
1122         :param list criterion: domain component specifying the search criterion on the field.
1123         :rtype: list
1124         :return: domain to use instead of ``criterion`` when performing the search.
1125                  This new domain must be based only on columns stored in the database, as it
1126                  will be used directly without any translation.
1127
1128         The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
1129         The most generic way to implement ``fnct_search`` is to directly search for the records that
1130         match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
1131         ``[('id','in',[1,3,5])]``.
1132
1133     .. _field-function-store:
1134
1135     .. rubric:: The ``store`` parameter
1136
1137     The ``store`` parameter allows caching the result of the field computation in the
1138     database, and defining the triggers that will invalidate that cache and force a
1139     recomputation of the function field.
1140     When not provided, the field is computed every time its value is read.
1141     The value of ``store`` may be either ``True`` (to recompute the field value whenever
1142     any field in the same record is modified), or a dictionary specifying a more
1143     flexible set of recomputation triggers.
1144
1145     A trigger specification is a dictionary that maps the names of the models that
1146     will trigger the computation, to a tuple describing the trigger rule, in the
1147     following form::
1148
1149         store = {
1150             'trigger_model': (mapping_function,
1151                               ['trigger_field1', 'trigger_field2'],
1152                               priority),
1153         }
1154
1155     A trigger rule is defined by a 3-item tuple where:
1156
1157         * The ``mapping_function`` is defined as follows:
1158
1159             .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
1160
1161                 Callable that maps record ids of a trigger model to ids of the
1162                 corresponding records in the source model (whose field values
1163                 need to be recomputed).
1164
1165                 :param orm model: trigger_model
1166                 :param list trigger_ids: ids of the records of trigger_model that were
1167                                          modified
1168                 :rtype: list
1169                 :return: list of ids of the source model whose function field values
1170                          need to be recomputed
1171
1172         * The second item is a list of the fields who should act as triggers for
1173           the computation. If an empty list is given, all fields will act as triggers.
1174         * The last item is the priority, used to order the triggers when processing them
1175           after any write operation on a model that has function field triggers. The
1176           default priority is 10.
1177
1178     In fact, setting store = True is the same as using the following trigger dict::
1179
1180         store = {
1181               'model_itself': (lambda self, cr, uid, ids, context: ids,
1182                                [],
1183                                10)
1184         }
1185
1186     """
1187     _classic_read = False
1188     _classic_write = False
1189     _prefetch = False
1190     _type = 'function'
1191     _properties = True
1192
1193     # function fields are not copied by default
1194     copy = False
1195
1196 #
1197 # multi: compute several fields in one call
1198 #
1199     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):
1200         _column.__init__(self, **args)
1201         self._obj = obj
1202         self._fnct = fnct
1203         self._fnct_inv = fnct_inv
1204         self._arg = arg
1205         self._multi = multi
1206         if 'relation' in args:
1207             self._obj = args['relation']
1208
1209         self.digits = args.get('digits', (16,2))
1210         self.digits_compute = args.get('digits_compute', None)
1211         if callable(args.get('selection')):
1212             from openerp import api
1213             self.selection = api.expected(api.cr_uid_context, args['selection'])
1214
1215         self._fnct_inv_arg = fnct_inv_arg
1216         if not fnct_inv:
1217             self.readonly = 1
1218         self._type = type
1219         self._fnct_search = fnct_search
1220         self.store = store
1221
1222         if not fnct_search and not store:
1223             self.selectable = False
1224
1225         if store:
1226             if self._type != 'many2one':
1227                 # m2o fields need to return tuples with name_get, not just foreign keys
1228                 self._classic_read = True
1229             self._classic_write = True
1230             if type=='binary':
1231                 self._symbol_get=lambda x:x and str(x)
1232             else:
1233                 self._prefetch = True
1234
1235         if type == 'char':
1236             self._symbol_c = char._symbol_c
1237             self._symbol_f = lambda x: _symbol_set_char(self, x)
1238             self._symbol_set = (self._symbol_c, self._symbol_f)
1239         else:
1240             type_class = globals().get(type)
1241             if type_class is not None:
1242                 self._symbol_c = type_class._symbol_c
1243                 self._symbol_f = type_class._symbol_f
1244                 self._symbol_set = type_class._symbol_set
1245
1246     def to_field_args(self):
1247         args = super(function, self).to_field_args()
1248         if self._type in ('float',):
1249             args['digits'] = self.digits_compute or self.digits
1250         elif self._type in ('selection', 'reference'):
1251             args['selection'] = self.selection
1252         elif self._type in ('many2one', 'one2many', 'many2many'):
1253             args['comodel_name'] = self._obj
1254         return args
1255
1256     def digits_change(self, cr):
1257         if self._type == 'float':
1258             if self.digits_compute:
1259                 self.digits = self.digits_compute(cr)
1260             if self.digits:
1261                 precision, scale = self.digits
1262                 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1263                                                                            precision_digits=scale),
1264                                                                precision_digits=scale))
1265
1266     def search(self, cr, uid, obj, name, args, context=None):
1267         if not self._fnct_search:
1268             #CHECKME: should raise an exception
1269             return []
1270         return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1271
1272     def postprocess(self, cr, uid, obj, field, value=None, context=None):
1273         return self._postprocess_batch(cr, uid, obj, field, {0: value}, context=context)[0]
1274
1275     def _postprocess_batch(self, cr, uid, obj, field, values, context=None):
1276         if not values:
1277             return values
1278
1279         if context is None:
1280             context = {}
1281
1282         field_type = obj._columns[field]._type
1283         new_values = dict(values)
1284
1285         if field_type == 'binary':
1286             if context.get('bin_size'):
1287                 # client requests only the size of binary fields
1288                 for rid, value in values.iteritems():
1289                     if value:
1290                         new_values[rid] = get_nice_size(value)
1291             elif not context.get('bin_raw'):
1292                 for rid, value in values.iteritems():
1293                     if value:
1294                         new_values[rid] = sanitize_binary_value(value)
1295
1296         return new_values
1297
1298     def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1299         multi = self._multi
1300         # if we already have a value, don't recompute it.
1301         # This happen if case of stored many2one fields
1302         if values and not multi and name in values[0]:
1303             result = dict((v['id'], v[name]) for v in values)
1304         elif values and multi and all(n in values[0] for n in name):
1305             result = dict((v['id'], dict((n, v[n]) for n in name)) for v in values)
1306         else:
1307             result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1308         if multi:
1309             swap = {}
1310             for rid, values in result.iteritems():
1311                 for f, v in values.iteritems():
1312                     if f not in name:
1313                         continue
1314                     swap.setdefault(f, {})[rid] = v
1315
1316             for field, values in swap.iteritems():
1317                 new_values = self._postprocess_batch(cr, uid, obj, field, values, context)
1318                 for rid, value in new_values.iteritems():
1319                     result[rid][field] = value
1320
1321         else:
1322             result = self._postprocess_batch(cr, uid, obj, name, result, context)
1323
1324         return result
1325
1326     def set(self, cr, obj, id, name, value, user=None, context=None):
1327         if not context:
1328             context = {}
1329         if self._fnct_inv:
1330             self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1331
1332     @classmethod
1333     def _as_display_name(cls, field, cr, uid, obj, value, context=None):
1334         # Function fields are supposed to emulate a basic field type,
1335         # so they can delegate to the basic type for record name rendering
1336         return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
1337
1338 # ---------------------------------------------------------
1339 # Related fields
1340 # ---------------------------------------------------------
1341
1342 class related(function):
1343     """Field that points to some data inside another field of the current record.
1344
1345     Example::
1346
1347        _columns = {
1348            'foo_id': fields.many2one('my.foo', 'Foo'),
1349            'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1350         }
1351     """
1352
1353     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1354         # assume self._arg = ('foo', 'bar', 'baz')
1355         # domain = [(name, op, val)]   =>   search [('foo.bar.baz', op, val)]
1356         field = '.'.join(self._arg)
1357         return map(lambda x: (field, x[1], x[2]), domain)
1358
1359     def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1360         if isinstance(ids, (int, long)):
1361             ids = [ids]
1362         for instance in obj.browse(cr, uid, ids, context=context):
1363             # traverse all fields except the last one
1364             for field in self.arg[:-1]:
1365                 instance = instance[field][:1]
1366             if instance:
1367                 # write on the last field of the target record
1368                 instance.write({self.arg[-1]: values})
1369
1370     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1371         res = {}
1372         for record in obj.browse(cr, SUPERUSER_ID, ids, context=context):
1373             value = record
1374             # traverse all fields except the last one
1375             for field in self.arg[:-1]:
1376                 value = value[field][:1]
1377             # read the last field on the target record
1378             res[record.id] = value[self.arg[-1]]
1379
1380         if self._type == 'many2one':
1381             # res[id] is a recordset; convert it to (id, name) or False.
1382             # Perform name_get as root, as seeing the name of a related object depends on
1383             # access right of source document, not target, so user may not have access.
1384             value_ids = list(set(value.id for value in res.itervalues() if value))
1385             value_name = dict(obj.pool[self._obj].name_get(cr, SUPERUSER_ID, value_ids, context=context))
1386             res = dict((id, bool(value) and (value.id, value_name[value.id])) for id, value in res.iteritems())
1387
1388         elif self._type in ('one2many', 'many2many'):
1389             # res[id] is a recordset; convert it to a list of ids
1390             res = dict((id, value.ids) for id, value in res.iteritems())
1391
1392         return res
1393
1394     def __init__(self, *arg, **args):
1395         self.arg = arg
1396         self._relations = []
1397         super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1398         if self.store is True:
1399             # TODO: improve here to change self.store = {...} according to related objects
1400             pass
1401
1402
1403 class sparse(function):   
1404
1405     def convert_value(self, obj, cr, uid, record, value, read_value, context=None):        
1406         """
1407             + For a many2many field, a list of tuples is expected.
1408               Here is the list of tuple that are accepted, with the corresponding semantics ::
1409
1410                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
1411                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
1412                  (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)
1413                  (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)
1414                  (4, ID)                link to existing record with id = ID (adds a relationship)
1415                  (5)                    unlink all (like using (3,ID) for all linked records)
1416                  (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1417
1418                  Example:
1419                     [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1420
1421             + For a one2many field, a lits of tuples is expected.
1422               Here is the list of tuple that are accepted, with the corresponding semantics ::
1423
1424                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
1425                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
1426                  (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)
1427
1428                  Example:
1429                     [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1430         """
1431
1432         if self._type == 'many2many':
1433             assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1434             return value[0][2]
1435
1436         elif self._type == 'one2many':
1437             if not read_value:
1438                 read_value = []
1439             relation_obj = obj.pool[self.relation]
1440             for vals in value:
1441                 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1442                 if vals[0] == 0:
1443                     read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1444                 elif vals[0] == 1:
1445                     relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1446                 elif vals[0] == 2:
1447                     relation_obj.unlink(cr, uid, vals[1], context=context)
1448                     read_value.remove(vals[1])
1449             return read_value
1450         return value
1451
1452
1453     def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1454         if not type(ids) == list:
1455             ids = [ids]
1456         records = obj.browse(cr, uid, ids, context=context)
1457         for record in records:
1458             # grab serialized value as object - already deserialized
1459             serialized = getattr(record, self.serialization_field)
1460             if value is None:
1461                 # simply delete the key to unset it.
1462                 serialized.pop(field_name, None)
1463             else: 
1464                 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1465             obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1466         return True
1467
1468     def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1469         results = {}
1470         records = obj.browse(cr, uid, ids, context=context)
1471         for record in records:
1472             # grab serialized value as object - already deserialized
1473             serialized = getattr(record, self.serialization_field)
1474             results[record.id] = {}
1475             for field_name in field_names:
1476                 field_type = obj._columns[field_name]._type
1477                 value = serialized.get(field_name, False)
1478                 if field_type in ('one2many','many2many'):
1479                     value = value or []
1480                     if value:
1481                         # filter out deleted records as superuser
1482                         relation_obj = obj.pool[obj._columns[field_name].relation]
1483                         value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1484                 if type(value) in (int,long) and field_type == 'many2one':
1485                     relation_obj = obj.pool[obj._columns[field_name].relation]
1486                     # check for deleted record as superuser
1487                     if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1488                         value = False
1489                 results[record.id][field_name] = value
1490         return results
1491
1492     def __init__(self, serialization_field, **kwargs):
1493         self.serialization_field = serialization_field
1494         super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1495      
1496
1497
1498 # ---------------------------------------------------------
1499 # Dummy fields
1500 # ---------------------------------------------------------
1501
1502 class dummy(function):
1503     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1504         return []
1505
1506     def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1507         return False
1508
1509     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1510         return {}
1511
1512     def __init__(self, *arg, **args):
1513         self.arg = arg
1514         self._relations = []
1515         super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1516
1517 # ---------------------------------------------------------
1518 # Serialized fields
1519 # ---------------------------------------------------------
1520
1521 class serialized(_column):
1522     """ A field able to store an arbitrary python data structure.
1523     
1524         Note: only plain components allowed.
1525     """
1526     
1527     def _symbol_set_struct(val):
1528         return simplejson.dumps(val)
1529
1530     def _symbol_get_struct(self, val):
1531         return simplejson.loads(val or '{}')
1532     
1533     _prefetch = False
1534     _type = 'serialized'
1535
1536     _symbol_c = '%s'
1537     _symbol_f = _symbol_set_struct
1538     _symbol_set = (_symbol_c, _symbol_f)
1539     _symbol_get = _symbol_get_struct
1540
1541 # TODO: review completly this class for speed improvement
1542 class property(function):
1543
1544     def to_field_args(self):
1545         args = super(property, self).to_field_args()
1546         args['company_dependent'] = True
1547         return args
1548
1549     def _fnct_search(self, tobj, cr, uid, obj, name, domain, context=None):
1550         ir_property = obj.pool['ir.property']
1551         result = []
1552         for field, operator, value in domain:
1553             result += ir_property.search_multi(cr, uid, name, tobj._name, operator, value, context=context)
1554         return result
1555
1556     def _fnct_write(self, obj, cr, uid, id, prop_name, value, obj_dest, context=None):
1557         ir_property = obj.pool['ir.property']
1558         ir_property.set_multi(cr, uid, prop_name, obj._name, {id: value}, context=context)
1559         return True
1560
1561     def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1562         ir_property = obj.pool['ir.property']
1563
1564         res = {id: {} for id in ids}
1565         for prop_name in prop_names:
1566             column = obj._all_columns[prop_name].column
1567             values = ir_property.get_multi(cr, uid, prop_name, obj._name, ids, context=context)
1568             if column._type == 'many2one':
1569                 # name_get the non-null values as SUPERUSER_ID
1570                 vals = sum(set(filter(None, values.itervalues())),
1571                            obj.pool[column._obj].browse(cr, uid, [], context=context))
1572                 vals_name = dict(vals.sudo().name_get()) if vals else {}
1573                 for id, value in values.iteritems():
1574                     ng = False
1575                     if value and value.id in vals_name:
1576                         ng = value.id, vals_name[value.id]
1577                     res[id][prop_name] = ng
1578             else:
1579                 for id, value in values.iteritems():
1580                     res[id][prop_name] = value
1581
1582         return res
1583
1584     def __init__(self, **args):
1585         if 'view_load' in args:
1586             _logger.warning("view_load attribute is deprecated on ir.fields. Args: %r", args)
1587         obj = 'relation' in args and args['relation'] or ''
1588         super(property, self).__init__(
1589             fnct=self._fnct_read,
1590             fnct_inv=self._fnct_write,
1591             fnct_search=self._fnct_search,
1592             obj=obj,
1593             multi='properties',
1594             **args
1595         )
1596
1597
1598 class column_info(object):
1599     """ Struct containing details about an osv column, either one local to
1600         its model, or one inherited via _inherits.
1601
1602         .. attribute:: name
1603
1604             name of the column
1605
1606         .. attribute:: column
1607
1608             column instance, subclass of :class:`_column`
1609
1610         .. attribute:: parent_model
1611
1612             if the column is inherited, name of the model that contains it,
1613             ``None`` for local columns.
1614
1615         .. attribute:: parent_column
1616
1617             the name of the column containing the m2o relationship to the
1618             parent model that contains this column, ``None`` for local columns.
1619
1620         .. attribute:: original_parent
1621
1622             if the column is inherited, name of the original parent model that
1623             contains it i.e in case of multilevel inheritance, ``None`` for
1624             local columns.
1625     """
1626     def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1627         self.name = name
1628         self.column = column
1629         self.parent_model = parent_model
1630         self.parent_column = parent_column
1631         self.original_parent = original_parent
1632
1633     def __str__(self):
1634         return '%s(%s, %s, %s, %s, %s)' % (
1635             self.__class__.__name__, self.name, self.column,
1636             self.parent_model, self.parent_column, self.original_parent)
1637
1638
1639 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: