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