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