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