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