fix
[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         dom = self._domain
518         if isinstance(self._domain, type(lambda: None)):
519             dom = self._domain(obj)
520         ids2 = obj.pool.get(self._obj).search(cr, user, dom + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
521         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
522             if r[self._fields_id] in res:
523                 res[r[self._fields_id]].append(r['id'])
524         return res
525
526     def set(self, cr, obj, id, field, values, user=None, context=None):
527         result = []
528         if not context:
529             context = {}
530         if self._context:
531             context = context.copy()
532         context.update(self._context)
533         context['no_store_function'] = True
534         if not values:
535             return
536         _table = obj.pool.get(self._obj)._table
537         obj = obj.pool.get(self._obj)
538         for act in values:
539             if act[0] == 0:
540                 act[2][self._fields_id] = id
541                 id_new = obj.create(cr, user, act[2], context=context)
542                 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
543             elif act[0] == 1:
544                 obj.write(cr, user, [act[1]], act[2], context=context)
545             elif act[0] == 2:
546                 obj.unlink(cr, user, [act[1]], context=context)
547             elif act[0] == 3:
548                 reverse_rel = obj._all_columns.get(self._fields_id)
549                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
550                 # if the model has on delete cascade, just delete the row
551                 if reverse_rel.column.ondelete == "cascade":
552                     obj.unlink(cr, user, [act[1]], context=context)
553                 else:
554                     cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
555             elif act[0] == 4:
556                 # Must use write() to recompute parent_store structure if needed
557                 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
558             elif act[0] == 5:
559                 reverse_rel = obj._all_columns.get(self._fields_id)
560                 assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
561                 # if the o2m has a static domain we must respect it when unlinking
562                 dom = self._domain
563                 if isinstance(self._domain, type(lambda: None)):
564                     dom = self._domain(obj)
565                 extra_domain = dom or []
566                 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
567                 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
568                 # otherwise we only nullify the reverse foreign key column.
569                 if reverse_rel.column.ondelete == "cascade":
570                     obj.unlink(cr, user, ids_to_unlink, context=context)
571                 else:
572                     obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
573             elif act[0] == 6:
574                 # Must use write() to recompute parent_store structure if needed
575                 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
576                 ids2 = act[2] or [0]
577                 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
578                 ids3 = map(lambda x:x[0], cr.fetchall())
579                 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
580         return result
581
582     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
583         dom = self._domain
584         if isinstance(self._domain, type(lambda: None)):
585             dom = self._domain(obj)
586         return obj.pool.get(self._obj).name_search(cr, uid, value, dom, operator, context=context,limit=limit)
587
588     
589     @classmethod
590     def _as_display_name(cls, field, cr, uid, obj, value, context=None):
591         raise NotImplementedError('One2Many columns should not be used as record name (_rec_name)') 
592
593 #
594 # Values: (0, 0,  { fields })    create
595 #         (1, ID, { fields })    update (write fields to ID)
596 #         (2, ID)                remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
597 #         (3, ID)                unlink (delete the relationship between the two objects but does not delete ID)
598 #         (4, ID)                link (add a relationship)
599 #         (5, ID)                unlink all
600 #         (6, ?, ids)            set a list of links
601 #
602 class many2many(_column):
603     """Encapsulates the logic of a many-to-many bidirectional relationship, handling the
604        low-level details of the intermediary relationship table transparently.
605        A many-to-many relationship is always symmetrical, and can be declared and accessed
606        from either endpoint model.
607        If ``rel`` (relationship table name), ``id1`` (source foreign key column name)
608        or id2 (destination foreign key column name) are not specified, the system will
609        provide default values. This will by default only allow one single symmetrical
610        many-to-many relationship between the source and destination model.
611        For multiple many-to-many relationship between the same models and for
612        relationships where source and destination models are the same, ``rel``, ``id1``
613        and ``id2`` should be specified explicitly.
614
615        :param str obj: destination model
616        :param str rel: optional name of the intermediary relationship table. If not specified,
617                        a canonical name will be derived based on the alphabetically-ordered
618                        model names of the source and destination (in the form: ``amodel_bmodel_rel``).
619                        Automatic naming is not possible when the source and destination are
620                        the same, for obvious ambiguity reasons.
621        :param str id1: optional name for the column holding the foreign key to the current
622                        model in the relationship table. If not specified, a canonical name
623                        will be derived based on the model name (in the form: `src_model_id`).
624        :param str id2: optional name for the column holding the foreign key to the destination
625                        model in the relationship table. If not specified, a canonical name
626                        will be derived based on the model name (in the form: `dest_model_id`)
627        :param str string: field label
628     """
629     _classic_read = False
630     _classic_write = False
631     _prefetch = False
632     _type = 'many2many'
633
634     def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
635         """
636         """
637         _column.__init__(self, string=string, **args)
638         self._obj = obj
639         if rel and '.' in rel:
640             raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
641                 'You used %s, which is not a valid SQL table name.')% (string,rel))
642         self._rel = rel
643         self._id1 = id1
644         self._id2 = id2
645         self._limit = limit
646
647     def _sql_names(self, source_model):
648         """Return the SQL names defining the structure of the m2m relationship table
649
650             :return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
651                      local_col is the name of the column holding the current model's FK, and
652                      dest_col is the name of the column holding the destination model's FK, and
653         """
654         tbl, col1, col2 = self._rel, self._id1, self._id2
655         if not all((tbl, col1, col2)):
656             # the default table name is based on the stable alphabetical order of tables
657             dest_model = source_model.pool.get(self._obj)
658             tables = tuple(sorted([source_model._table, dest_model._table]))
659             if not tbl:
660                 assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
661                                                'is not possible when source and destination models are '\
662                                                'the same'
663                 tbl = '%s_%s_rel' % tables
664             if not col1:
665                 col1 = '%s_id' % source_model._table
666             if not col2:
667                 col2 = '%s_id' % dest_model._table
668         return (tbl, col1, col2)
669
670     def _get_query_and_where_params(self, cr, model, ids, values, where_params):
671         """ Extracted from ``get`` to facilitate fine-tuning of the generated
672             query. """
673         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
674                    FROM %(rel)s, %(from_c)s \
675                   WHERE %(rel)s.%(id1)s IN %%s \
676                     AND %(rel)s.%(id2)s = %(tbl)s.id \
677                  %(where_c)s  \
678                  %(order_by)s \
679                  %(limit)s \
680                  OFFSET %(offset)d' \
681                  % values
682         return query, where_params
683
684     def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
685         if not context:
686             context = {}
687         if not values:
688             values = {}
689         res = {}
690         if not ids:
691             return res
692         for id in ids:
693             res[id] = []
694         if offset:
695             _logger.warning(
696                 "Specifying offset at a many2many.get() is deprecated and may"
697                 " produce unpredictable results.")
698         obj = model.pool.get(self._obj)
699         rel, id1, id2 = self._sql_names(model)
700
701         # static domains are lists, and are evaluated both here and on client-side, while string
702         # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
703         # FIXME: make this distinction explicit in API!
704         domain = isinstance(self._domain, list) and self._domain or []
705
706         wquery = obj._where_calc(cr, user, domain, context=context)
707         obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
708         from_c, where_c, where_params = wquery.get_sql()
709         if where_c:
710             where_c = ' AND ' + where_c
711
712         if offset or self._limit:
713             order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
714         else:
715             order_by = ''
716
717         limit_str = ''
718         if self._limit is not None:
719             limit_str = ' LIMIT %d' % self._limit
720
721         query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel,
722                'from_c': from_c,
723                'tbl': obj._table,
724                'id1': id1,
725                'id2': id2,
726                'where_c': where_c,
727                'limit': limit_str,
728                'order_by': order_by,
729                'offset': offset,
730                 }, where_params)
731
732         cr.execute(query, [tuple(ids),] + where_params)
733         for r in cr.fetchall():
734             res[r[1]].append(r[0])
735         return res
736
737     def set(self, cr, model, id, name, values, user=None, context=None):
738         if not context:
739             context = {}
740         if not values:
741             return
742         rel, id1, id2 = self._sql_names(model)
743         obj = model.pool.get(self._obj)
744         for act in values:
745             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
746                 continue
747             if act[0] == 0:
748                 idnew = obj.create(cr, user, act[2], context=context)
749                 cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
750             elif act[0] == 1:
751                 obj.write(cr, user, [act[1]], act[2], context=context)
752             elif act[0] == 2:
753                 obj.unlink(cr, user, [act[1]], context=context)
754             elif act[0] == 3:
755                 cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
756             elif act[0] == 4:
757                 # following queries are in the same transaction - so should be relatively safe
758                 cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
759                 if not cr.fetchone():
760                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
761             elif act[0] == 5:
762                 cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
763             elif act[0] == 6:
764
765                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
766                 if d1:
767                     d1 = ' and ' + ' and '.join(d1)
768                 else:
769                     d1 = ''
770                 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)
771
772                 for act_nbr in act[2]:
773                     cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
774
775     #
776     # TODO: use a name_search
777     #
778     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
779         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
780
781     @classmethod
782     def _as_display_name(cls, field, cr, uid, obj, value, context=None):
783         raise NotImplementedError('Many2Many columns should not be used as record name (_rec_name)') 
784
785
786 def get_nice_size(value):
787     size = 0
788     if isinstance(value, (int,long)):
789         size = value
790     elif value: # this is supposed to be a string
791         size = len(value)
792     return tools.human_size(size)
793
794 # See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
795 # and http://bugs.python.org/issue10066
796 invalid_xml_low_bytes = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
797
798 def sanitize_binary_value(value):
799     # binary fields should be 7-bit ASCII base64-encoded data,
800     # but we do additional sanity checks to make sure the values
801     # are not something else that won't pass via XML-RPC
802     if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
803         # these builtin types are meant to pass untouched
804         return value
805
806     # Handle invalid bytes values that will cause problems
807     # for XML-RPC. See for more info:
808     #  - http://bugs.python.org/issue10066
809     #  - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
810
811     # Coercing to unicode would normally allow it to properly pass via
812     # XML-RPC, transparently encoded as UTF-8 by xmlrpclib.
813     # (this works for _any_ byte values, thanks to the fallback
814     #  to latin-1 passthrough encoding when decoding to unicode)
815     value = tools.ustr(value)
816
817     # Due to Python bug #10066 this could still yield invalid XML
818     # bytes, specifically in the low byte range, that will crash
819     # the decoding side: [\x00-\x08\x0b-\x0c\x0e-\x1f]
820     # So check for low bytes values, and if any, perform
821     # base64 encoding - not very smart or useful, but this is
822     # our last resort to avoid crashing the request.
823     if invalid_xml_low_bytes.search(value):
824         # b64-encode after restoring the pure bytes with latin-1
825         # passthrough encoding
826         value = base64.b64encode(value.encode('latin-1'))
827
828     return value
829
830
831 # ---------------------------------------------------------
832 # Function fields
833 # ---------------------------------------------------------
834 class function(_column):
835     """
836     A field whose value is computed by a function (rather
837     than being read from the database).
838
839     :param fnct: the callable that will compute the field value.
840     :param arg: arbitrary value to be passed to ``fnct`` when computing the value.
841     :param fnct_inv: the callable that will allow writing values in that field
842                      (if not provided, the field is read-only).
843     :param fnct_inv_arg: arbitrary value to be passed to ``fnct_inv`` when
844                          writing a value.
845     :param str type: type of the field simulated by the function field
846     :param fnct_search: the callable that allows searching on the field
847                         (if not provided, search will not return any result).
848     :param store: store computed value in database
849                   (see :ref:`The *store* parameter <field-function-store>`).
850     :type store: True or dict specifying triggers for field computation
851     :param multi: name of batch for batch computation of function fields.
852                   All fields with the same batch name will be computed by
853                   a single function call. This changes the signature of the
854                   ``fnct`` callable.
855
856     .. _field-function-fnct: The ``fnct`` parameter
857
858     .. rubric:: The ``fnct`` parameter
859
860     The callable implementing the function field must have the following signature:
861
862     .. function:: fnct(model, cr, uid, ids, field_name(s), arg, context)
863
864         Implements the function field.
865
866         :param orm model: model to which the field belongs (should be ``self`` for
867                           a model method)
868         :param field_name(s): name of the field to compute, or if ``multi`` is provided,
869                               list of field names to compute.
870         :type field_name(s): str | [str]
871         :param arg: arbitrary value passed when declaring the function field
872         :rtype: dict
873         :return: mapping of ``ids`` to computed values, or if multi is provided,
874                  to a map of field_names to computed values
875
876     The values in the returned dictionary must be of the type specified by the type
877     argument in the field declaration.
878
879     Here is an example with a simple function ``char`` function field::
880
881         # declarations
882         def compute(self, cr, uid, ids, field_name, arg, context):
883             result = {}
884             # ...
885             return result
886         _columns['my_char'] = fields.function(compute, type='char', size=50)
887
888         # when called with ``ids=[1,2,3]``, ``compute`` could return:
889         {
890             1: 'foo',
891             2: 'bar',
892             3: False # null values should be returned explicitly too
893         }
894
895     If ``multi`` is set, then ``field_name`` is replaced by ``field_names``: a list
896     of the field names that should be computed. Each value in the returned
897     dictionary must then be a dictionary mapping field names to values.
898
899     Here is an example where two function fields (``name`` and ``age``)
900     are both computed by a single function field::
901
902         # declarations
903         def compute(self, cr, uid, ids, field_names, arg, context):
904             result = {}
905             # ...
906             return result
907         _columns['name'] = fields.function(compute_person_data, type='char',\
908                                            size=50, multi='person_data')
909         _columns[''age'] = fields.function(compute_person_data, type='integer',\
910                                            multi='person_data')
911
912         # when called with ``ids=[1,2,3]``, ``compute_person_data`` could return:
913         {
914             1: {'name': 'Bob', 'age': 23},
915             2: {'name': 'Sally', 'age': 19},
916             3: {'name': 'unknown', 'age': False}
917         }
918
919     .. _field-function-fnct-inv:
920
921     .. rubric:: The ``fnct_inv`` parameter
922
923     This callable implements the write operation for the function field
924     and must have the following signature:
925
926     .. function:: fnct_inv(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context)
927
928         Callable that implements the ``write`` operation for the function field.
929
930         :param orm model: model to which the field belongs (should be ``self`` for
931                           a model method)
932         :param int id: the identifier of the object to write on
933         :param str field_name: name of the field to set
934         :param fnct_inv_arg: arbitrary value passed when declaring the function field
935         :return: True
936
937     When writing values for a function field, the ``multi`` parameter is ignored.
938
939     .. _field-function-fnct-search:
940
941     .. rubric:: The ``fnct_search`` parameter
942
943     This callable implements the search operation for the function field
944     and must have the following signature:
945
946     .. function:: fnct_search(model, cr, uid, model_again, field_name, criterion, context)
947
948         Callable that implements the ``search`` operation for the function field by expanding
949         a search criterion based on the function field into a new domain based only on
950         columns that are stored in the database.
951
952         :param orm model: model to which the field belongs (should be ``self`` for
953                           a model method)
954         :param orm model_again: same value as ``model`` (seriously! this is for backwards
955                                 compatibility)
956         :param str field_name: name of the field to search on
957         :param list criterion: domain component specifying the search criterion on the field.
958         :rtype: list
959         :return: domain to use instead of ``criterion`` when performing the search.
960                  This new domain must be based only on columns stored in the database, as it
961                  will be used directly without any translation.
962
963         The returned value must be a domain, that is, a list of the form [(field_name, operator, operand)].
964         The most generic way to implement ``fnct_search`` is to directly search for the records that
965         match the given ``criterion``, and return their ``ids`` wrapped in a domain, such as
966         ``[('id','in',[1,3,5])]``.
967
968     .. _field-function-store:
969
970     .. rubric:: The ``store`` parameter
971
972     The ``store`` parameter allows caching the result of the field computation in the
973     database, and defining the triggers that will invalidate that cache and force a
974     recomputation of the function field.
975     When not provided, the field is computed every time its value is read.
976     The value of ``store`` may be either ``True`` (to recompute the field value whenever
977     any field in the same record is modified), or a dictionary specifying a more
978     flexible set of recomputation triggers.
979
980     A trigger specification is a dictionary that maps the names of the models that
981     will trigger the computation, to a tuple describing the trigger rule, in the
982     following form::
983
984         store = {
985             'trigger_model': (mapping_function,
986                               ['trigger_field1', 'trigger_field2'],
987                               priority),
988         }
989
990     A trigger rule is defined by a 3-item tuple where:
991
992         * The ``mapping_function`` is defined as follows:
993
994             .. function:: mapping_function(trigger_model, cr, uid, trigger_ids, context)
995
996                 Callable that maps record ids of a trigger model to ids of the
997                 corresponding records in the source model (whose field values
998                 need to be recomputed).
999
1000                 :param orm model: trigger_model
1001                 :param list trigger_ids: ids of the records of trigger_model that were
1002                                          modified
1003                 :rtype: list
1004                 :return: list of ids of the source model whose function field values
1005                          need to be recomputed
1006
1007         * The second item is a list of the fields who should act as triggers for
1008           the computation. If an empty list is given, all fields will act as triggers.
1009         * The last item is the priority, used to order the triggers when processing them
1010           after any write operation on a model that has function field triggers. The
1011           default priority is 10.
1012
1013     In fact, setting store = True is the same as using the following trigger dict::
1014
1015         store = {
1016               'model_itself': (lambda self, cr, uid, ids, context: ids,
1017                                [],
1018                                10)
1019         }
1020
1021     """
1022     _classic_read = False
1023     _classic_write = False
1024     _prefetch = False
1025     _type = 'function'
1026     _properties = True
1027
1028 #
1029 # multi: compute several fields in one call
1030 #
1031     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):
1032         _column.__init__(self, **args)
1033         self._obj = obj
1034         self._fnct = fnct
1035         self._fnct_inv = fnct_inv
1036         self._arg = arg
1037         self._multi = multi
1038         if 'relation' in args:
1039             self._obj = args['relation']
1040
1041         self.digits = args.get('digits', (16,2))
1042         self.digits_compute = args.get('digits_compute', None)
1043
1044         self._fnct_inv_arg = fnct_inv_arg
1045         if not fnct_inv:
1046             self.readonly = 1
1047         self._type = type
1048         self._fnct_search = fnct_search
1049         self.store = store
1050
1051         if not fnct_search and not store:
1052             self.selectable = False
1053
1054         if store:
1055             if self._type != 'many2one':
1056                 # m2o fields need to return tuples with name_get, not just foreign keys
1057                 self._classic_read = True
1058             self._classic_write = True
1059             if type=='binary':
1060                 self._symbol_get=lambda x:x and str(x)
1061
1062         if type == 'float':
1063             self._symbol_c = float._symbol_c
1064             self._symbol_f = float._symbol_f
1065             self._symbol_set = float._symbol_set
1066
1067         if type == 'boolean':
1068             self._symbol_c = boolean._symbol_c
1069             self._symbol_f = boolean._symbol_f
1070             self._symbol_set = boolean._symbol_set
1071
1072         if type == 'integer':
1073             self._symbol_c = integer._symbol_c
1074             self._symbol_f = integer._symbol_f
1075             self._symbol_set = integer._symbol_set
1076
1077     def digits_change(self, cr):
1078         if self._type == 'float':
1079             if self.digits_compute:
1080                 self.digits = self.digits_compute(cr)
1081             if self.digits:
1082                 precision, scale = self.digits
1083                 self._symbol_set = ('%s', lambda x: float_repr(float_round(__builtin__.float(x or 0.0),
1084                                                                            precision_digits=scale),
1085                                                                precision_digits=scale))
1086
1087     def search(self, cr, uid, obj, name, args, context=None):
1088         if not self._fnct_search:
1089             #CHECKME: should raise an exception
1090             return []
1091         return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
1092
1093     def postprocess(self, cr, uid, obj, field, value=None, context=None):
1094         if context is None:
1095             context = {}
1096         result = value
1097         field_type = obj._columns[field]._type
1098         if field_type == "many2one":
1099             # make the result a tuple if it is not already one
1100             if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
1101                 obj_model = obj.pool.get(obj._columns[field].relation)
1102                 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
1103                 result = (value, dict_names[value])
1104
1105         if field_type == 'binary':
1106             if context.get('bin_size'):
1107                 # client requests only the size of binary fields
1108                 result = get_nice_size(value)
1109             elif not context.get('bin_raw'):
1110                 result = sanitize_binary_value(value)
1111
1112         if field_type == "integer" and value > xmlrpclib.MAXINT:
1113             # integer/long values greater than 2^31-1 are not supported
1114             # in pure XMLRPC, so we have to pass them as floats :-(
1115             # This is not needed for stored fields and non-functional integer
1116             # fields, as their values are constrained by the database backend
1117             # to the same 32bits signed int limit.
1118             result = __builtin__.float(value)
1119         return result
1120
1121     def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
1122         result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
1123         for id in ids:
1124             if self._multi and id in result:
1125                 for field, value in result[id].iteritems():
1126                     if value:
1127                         result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
1128             elif result.get(id):
1129                 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
1130         return result
1131
1132     def set(self, cr, obj, id, name, value, user=None, context=None):
1133         if not context:
1134             context = {}
1135         if self._fnct_inv:
1136             self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
1137
1138     @classmethod
1139     def _as_display_name(cls, field, cr, uid, obj, value, context=None):
1140         # Function fields are supposed to emulate a basic field type,
1141         # so they can delegate to the basic type for record name rendering
1142         return globals()[field._type]._as_display_name(field, cr, uid, obj, value, context=context)
1143
1144 # ---------------------------------------------------------
1145 # Related fields
1146 # ---------------------------------------------------------
1147
1148 class related(function):
1149     """Field that points to some data inside another field of the current record.
1150
1151     Example::
1152
1153        _columns = {
1154            'foo_id': fields.many2one('my.foo', 'Foo'),
1155            'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
1156         }
1157     """
1158
1159     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1160         self._field_get2(cr, uid, obj, context)
1161         i = len(self._arg)-1
1162         sarg = name
1163         while i>0:
1164             if type(sarg) in [type([]), type( (1,) )]:
1165                 where = [(self._arg[i], 'in', sarg)]
1166             else:
1167                 where = [(self._arg[i], '=', sarg)]
1168             if domain:
1169                 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
1170                 domain = []
1171             sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
1172             i -= 1
1173         return [(self._arg[0], 'in', sarg)]
1174
1175     def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
1176         self._field_get2(cr, uid, obj, context=context)
1177         if type(ids) != type([]):
1178             ids=[ids]
1179         objlst = obj.browse(cr, uid, ids)
1180         for data in objlst:
1181             t_id = data.id
1182             t_data = data
1183             for i in range(len(self.arg)):
1184                 if not t_data: break
1185                 field_detail = self._relations[i]
1186                 if not t_data[self.arg[i]]:
1187                     if self._type not in ('one2many', 'many2many'):
1188                         t_id = t_data['id']
1189                     t_data = False
1190                 elif field_detail['type'] in ('one2many', 'many2many'):
1191                     if self._type != "many2one":
1192                         t_id = t_data.id
1193                         t_data = t_data[self.arg[i]][0]
1194                     else:
1195                         t_data = False
1196                 else:
1197                     t_id = t_data['id']
1198                     t_data = t_data[self.arg[i]]
1199             else:
1200                 model = obj.pool.get(self._relations[-1]['object'])
1201                 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
1202
1203     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1204         self._field_get2(cr, uid, obj, context)
1205         if not ids: return {}
1206         relation = obj._name
1207         if self._type in ('one2many', 'many2many'):
1208             res = dict([(i, []) for i in ids])
1209         else:
1210             res = {}.fromkeys(ids, False)
1211
1212         objlst = obj.browse(cr, 1, ids, context=context)
1213         for data in objlst:
1214             if not data:
1215                 continue
1216             t_data = data
1217             relation = obj._name
1218             for i in range(len(self.arg)):
1219                 field_detail = self._relations[i]
1220                 relation = field_detail['object']
1221                 try:
1222                     if not t_data[self.arg[i]]:
1223                         t_data = False
1224                         break
1225                 except:
1226                     t_data = False
1227                     break
1228                 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
1229                     t_data = t_data[self.arg[i]][0]
1230                 elif t_data:
1231                     t_data = t_data[self.arg[i]]
1232             if type(t_data) == type(objlst[0]):
1233                 res[data.id] = t_data.id
1234             elif t_data:
1235                 res[data.id] = t_data
1236         if self._type=='many2one':
1237             ids = filter(None, res.values())
1238             if ids:
1239                 # name_get as root, as seeing the name of a related
1240                 # object depends on access right of source document,
1241                 # not target, so user may not have access.
1242                 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
1243                 for r in res:
1244                     if res[r]:
1245                         res[r] = (res[r], ng[res[r]])
1246         elif self._type in ('one2many', 'many2many'):
1247             for r in res:
1248                 if res[r]:
1249                     res[r] = [x.id for x in res[r]]
1250         return res
1251
1252     def __init__(self, *arg, **args):
1253         self.arg = arg
1254         self._relations = []
1255         super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=self._fnct_search, **args)
1256         if self.store is True:
1257             # TODO: improve here to change self.store = {...} according to related objects
1258             pass
1259
1260     def _field_get2(self, cr, uid, obj, context=None):
1261         if self._relations:
1262             return
1263         result = []
1264         obj_name = obj._name
1265         for i in range(len(self._arg)):
1266             f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
1267             result.append({
1268                 'object': obj_name,
1269                 'type': f['type']
1270
1271             })
1272             if f.get('relation',False):
1273                 obj_name = f['relation']
1274                 result[-1]['relation'] = f['relation']
1275         self._relations = result
1276
1277 class sparse(function):   
1278
1279     def convert_value(self, obj, cr, uid, record, value, read_value, context=None):        
1280         """
1281             + For a many2many field, a list of tuples is expected.
1282               Here is the list of tuple that are accepted, with the corresponding semantics ::
1283
1284                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
1285                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
1286                  (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)
1287                  (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)
1288                  (4, ID)                link to existing record with id = ID (adds a relationship)
1289                  (5)                    unlink all (like using (3,ID) for all linked records)
1290                  (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
1291
1292                  Example:
1293                     [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
1294
1295             + For a one2many field, a lits of tuples is expected.
1296               Here is the list of tuple that are accepted, with the corresponding semantics ::
1297
1298                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
1299                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
1300                  (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)
1301
1302                  Example:
1303                     [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
1304         """
1305
1306         if self._type == 'many2many':
1307             assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
1308             return value[0][2]
1309
1310         elif self._type == 'one2many':
1311             if not read_value:
1312                 read_value = []
1313             relation_obj = obj.pool.get(self.relation)
1314             for vals in value:
1315                 assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
1316                 if vals[0] == 0:
1317                     read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
1318                 elif vals[0] == 1:
1319                     relation_obj.write(cr, uid, vals[1], vals[2], context=context)
1320                 elif vals[0] == 2:
1321                     relation_obj.unlink(cr, uid, vals[1], context=context)
1322                     read_value.remove(vals[1])
1323             return read_value
1324         return value
1325
1326
1327     def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
1328         if not type(ids) == list:
1329             ids = [ids]
1330         records = obj.browse(cr, uid, ids, context=context)
1331         for record in records:
1332             # grab serialized value as object - already deserialized
1333             serialized = getattr(record, self.serialization_field)
1334             if value is None:
1335                 # simply delete the key to unset it.
1336                 serialized.pop(field_name, None)
1337             else: 
1338                 serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
1339             obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
1340         return True
1341
1342     def _fnct_read(self, obj, cr, uid, ids, field_names, args, context=None):
1343         results = {}
1344         records = obj.browse(cr, uid, ids, context=context)
1345         for record in records:
1346             # grab serialized value as object - already deserialized
1347             serialized = getattr(record, self.serialization_field)
1348             results[record.id] = {}
1349             for field_name in field_names:
1350                 field_type = obj._columns[field_name]._type
1351                 value = serialized.get(field_name, False)
1352                 if field_type in ('one2many','many2many'):
1353                     value = value or []
1354                     if value:
1355                         # filter out deleted records as superuser
1356                         relation_obj = obj.pool.get(obj._columns[field_name].relation)
1357                         value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
1358                 if type(value) in (int,long) and field_type == 'many2one':
1359                     relation_obj = obj.pool.get(obj._columns[field_name].relation)
1360                     # check for deleted record as superuser
1361                     if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
1362                         value = False
1363                 results[record.id][field_name] = value
1364         return results
1365
1366     def __init__(self, serialization_field, **kwargs):
1367         self.serialization_field = serialization_field
1368         return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
1369      
1370
1371
1372 # ---------------------------------------------------------
1373 # Dummy fields
1374 # ---------------------------------------------------------
1375
1376 class dummy(function):
1377     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
1378         return []
1379
1380     def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
1381         return False
1382
1383     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
1384         return {}
1385
1386     def __init__(self, *arg, **args):
1387         self.arg = arg
1388         self._relations = []
1389         super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, fnct_search=None, **args)
1390
1391 # ---------------------------------------------------------
1392 # Serialized fields
1393 # ---------------------------------------------------------
1394
1395 class serialized(_column):
1396     """ A field able to store an arbitrary python data structure.
1397     
1398         Note: only plain components allowed.
1399     """
1400     
1401     def _symbol_set_struct(val):
1402         return simplejson.dumps(val)
1403
1404     def _symbol_get_struct(self, val):
1405         return simplejson.loads(val or '{}')
1406     
1407     _prefetch = False
1408     _type = 'serialized'
1409
1410     _symbol_c = '%s'
1411     _symbol_f = _symbol_set_struct
1412     _symbol_set = (_symbol_c, _symbol_f)
1413     _symbol_get = _symbol_get_struct
1414
1415 # TODO: review completly this class for speed improvement
1416 class property(function):
1417
1418     def _get_default(self, obj, cr, uid, prop_name, context=None):
1419         return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
1420
1421     def _get_defaults(self, obj, cr, uid, prop_names, context=None):
1422         """Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
1423
1424            :param list of string prop_names: list of name of property fields for those we want the default value
1425            :return: map of property field names to their default value
1426            :rtype: dict
1427         """
1428         prop = obj.pool.get('ir.property')
1429         res = {}
1430         for prop_name in prop_names:
1431             res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
1432         return res
1433
1434     def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
1435         prop = obj.pool.get('ir.property')
1436         vids = [obj._name + ',' + str(oid) for oid in  ids]
1437
1438         domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
1439         #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1440         if vids:
1441             domain = [('res_id', 'in', vids)] + domain
1442         return prop.search(cr, uid, domain, context=context)
1443
1444     # TODO: to rewrite more clean
1445     def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1446         if context is None:
1447             context = {}
1448
1449         nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1450         if nids:
1451             cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1452
1453         default_val = self._get_default(obj, cr, uid, prop_name, context)
1454
1455         property_create = False
1456         if isinstance(default_val, openerp.osv.orm.browse_record):
1457             if default_val.id != id_val:
1458                 property_create = True
1459         elif default_val != id_val:
1460             property_create = True
1461
1462         if property_create:
1463             def_id = self._field_get(cr, uid, obj._name, prop_name)
1464             company = obj.pool.get('res.company')
1465             cid = company._company_default_get(cr, uid, obj._name, def_id,
1466                                                context=context)
1467             propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1468                                                              context=context)
1469             prop = obj.pool.get('ir.property')
1470             return prop.create(cr, uid, {
1471                 'name': propdef.name,
1472                 'value': id_val,
1473                 'res_id': obj._name+','+str(id),
1474                 'company_id': cid,
1475                 'fields_id': def_id,
1476                 'type': self._type,
1477             }, context=context)
1478         return False
1479
1480     def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
1481         prop = obj.pool.get('ir.property')
1482         # get the default values (for res_id = False) for the property fields
1483         default_val = self._get_defaults(obj, cr, uid, prop_names, context)
1484
1485         # build the dictionary that will be returned
1486         res = {}
1487         for id in ids:
1488             res[id] = default_val.copy()
1489
1490         for prop_name in prop_names:
1491             property_field = obj._all_columns.get(prop_name).column
1492             property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
1493             # If the property field is a m2o field, we will append the id of the value to name_get_ids
1494             # in order to make a name_get in batch for all the ids needed.
1495             name_get_ids = {}
1496             for id in ids:
1497                 # get the result of ir.property.get() for this res_id and save it in res if it's existing
1498                 obj_reference = obj._name + ',' + str(id)
1499                 value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
1500                 if value:
1501                     res[id][prop_name] = value
1502                 # Check existence as root (as seeing the name of a related
1503                 # object depends on access right of source document,
1504                 # not target, so user may not have access) in order to avoid
1505                 # pointing on an unexisting record.
1506                 if property_destination_obj:
1507                     if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
1508                         name_get_ids[id] = res[id][prop_name].id
1509                     else:
1510                         res[id][prop_name] = False
1511             if property_destination_obj:
1512                 # name_get as root (as seeing the name of a related
1513                 # object depends on access right of source document,
1514                 # not target, so user may not have access.)
1515                 name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
1516                 # the property field is a m2o, we need to return a tuple with (id, name)
1517                 for k, v in name_get_ids.iteritems():
1518                     if res[k][prop_name]:
1519                         res[k][prop_name] = (v , name_get_values.get(v))
1520         return res
1521
1522     def _field_get(self, cr, uid, model_name, prop):
1523         if not self.field_id.get(cr.dbname):
1524             cr.execute('SELECT id \
1525                     FROM ir_model_fields \
1526                     WHERE name=%s AND model=%s', (prop, model_name))
1527             res = cr.fetchone()
1528             self.field_id[cr.dbname] = res and res[0]
1529         return self.field_id[cr.dbname]
1530
1531     def __init__(self, obj_prop, **args):
1532         # TODO remove obj_prop parameter (use many2one type)
1533         self.field_id = {}
1534         function.__init__(self, self._fnct_read, False, self._fnct_write,
1535                           obj_prop, multi='properties', **args)
1536
1537     def restart(self):
1538         self.field_id = {}
1539
1540
1541 def field_to_dict(model, cr, user, field, context=None):
1542     """ Return a dictionary representation of a field.
1543
1544     The string, help, and selection attributes (if any) are untranslated.  This
1545     representation is the one returned by fields_get() (fields_get() will do
1546     the translation).
1547
1548     """
1549
1550     res = {'type': field._type}
1551     # This additional attributes for M2M and function field is added
1552     # because we need to display tooltip with this additional information
1553     # when client is started in debug mode.
1554     if isinstance(field, function):
1555         res['function'] = field._fnct and field._fnct.func_name or False
1556     #    res['store'] = field.store
1557         if isinstance(field.store, dict):
1558             res['store'] = str(field.store)
1559         res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1560         res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1561         res['fnct_inv_arg'] = field._fnct_inv_arg or False
1562         res['func_obj'] = field._obj or False
1563     if isinstance(field, many2many):
1564         (table, col1, col2) = field._sql_names(model)
1565         res['related_columns'] = [col1, col2]
1566         res['third_table'] = table
1567     for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1568             'change_default', 'translate', 'help', 'select', 'selectable', 'groups'):
1569         if getattr(field, arg):
1570             res[arg] = getattr(field, arg)
1571     for arg in ('digits', 'invisible', 'filters'):
1572         if getattr(field, arg, None):
1573             res[arg] = getattr(field, arg)
1574
1575     if field.string:
1576         res['string'] = field.string
1577     if field.help:
1578         res['help'] = field.help
1579
1580     if hasattr(field, 'selection'):
1581         if isinstance(field.selection, (tuple, list)):
1582             res['selection'] = field.selection
1583         else:
1584             # call the 'dynamic selection' function
1585             res['selection'] = field.selection(model, cr, user, context)
1586     if res['type'] in ('one2many', 'many2many', 'many2one'):
1587         res['relation'] = field._obj
1588         res['domain'] = field._domain
1589         res['context'] = field._context
1590
1591     if isinstance(field, one2many):
1592         res['relation_field'] = field._fields_id
1593
1594     return res
1595
1596
1597 class column_info(object):
1598     """Struct containing details about an osv column, either one local to
1599        its model, or one inherited via _inherits.
1600
1601        :attr name: name of the column
1602        :attr column: column instance, subclass of osv.fields._column
1603        :attr parent_model: if the column is inherited, name of the model
1604                            that contains it, None for local columns.
1605        :attr parent_column: the name of the column containing the m2o
1606                             relationship to the parent model that contains
1607                             this column, None for local columns.
1608        :attr original_parent: if the column is inherited, name of the original
1609                             parent model that contains it i.e in case of multilevel
1610                             inheritence, None for local columns.
1611     """
1612     def __init__(self, name, column, parent_model=None, parent_column=None, original_parent=None):
1613         self.name = name
1614         self.column = column
1615         self.parent_model = parent_model
1616         self.parent_column = parent_column
1617         self.original_parent = original_parent
1618
1619 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1620