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