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