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