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