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