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