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