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