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