[FIX-692891]
[odoo/odoo.git] / bin / 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 import datetime as DT
35 import string
36 import sys
37 import warnings
38 import xmlrpclib
39 from psycopg2 import Binary
40
41 import netsvc
42 import tools
43 from tools.translate import _
44
45 def _symbol_set(symb):
46     if symb == None or symb == False:
47         return None
48     elif isinstance(symb, unicode):
49         return symb.encode('utf-8')
50     return str(symb)
51
52
53 class _column(object):
54     _classic_read = True
55     _classic_write = True
56     _prefetch = True
57     _properties = False
58     _type = 'unknown'
59     _obj = None
60     _multi = False
61     _symbol_c = '%s'
62     _symbol_f = _symbol_set
63     _symbol_set = (_symbol_c, _symbol_f)
64     _symbol_get = None
65
66     def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete="set null", translate=False, select=False, **args):
67         if domain is None:
68             domain = []
69         if context is None:
70             context = {}
71         self.states = states or {}
72         self.string = string
73         self.readonly = readonly
74         self.required = required
75         self.size = size
76         self.help = args.get('help', '')
77         self.priority = priority
78         self.change_default = change_default
79         self.ondelete = ondelete
80         self.translate = translate
81         self._domain = domain
82         self._context = context
83         self.write = False
84         self.read = False
85         self.view_load = 0
86         self.select = select
87         self.selectable = True
88         self.group_operator = args.get('group_operator', False)
89         for a in args:
90             if args[a]:
91                 setattr(self, a, args[a])
92
93     def restart(self):
94         pass
95
96     def set(self, cr, obj, id, name, value, user=None, context=None):
97         cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
98
99     def set_memory(self, cr, obj, id, name, value, user=None, context=None):
100         raise Exception(_('Not implemented set_memory method !'))
101
102     def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
103         raise Exception(_('Not implemented get_memory method !'))
104
105     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
106         raise Exception(_('undefined get method !'))
107
108     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
109         ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
110         res = obj.read(cr, uid, ids, [name], context=context)
111         return [x[name] for x in res]
112
113     def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
114         raise Exception(_('Not implemented search_memory method !'))
115
116
117 # ---------------------------------------------------------
118 # Simple fields
119 # ---------------------------------------------------------
120 class boolean(_column):
121     _type = 'boolean'
122     _symbol_c = '%s'
123     _symbol_f = lambda x: x and 'True' or 'False'
124     _symbol_set = (_symbol_c, _symbol_f)
125
126 class integer_big(_column):
127     _type = 'integer_big'
128     _symbol_c = '%s'
129     _symbol_f = lambda x: int(x or 0)
130     _symbol_set = (_symbol_c, _symbol_f)
131     _symbol_get = lambda self,x: x or 0
132
133 class integer(_column):
134     _type = 'integer'
135     _symbol_c = '%s'
136     _symbol_f = lambda x: int(x or 0)
137     _symbol_set = (_symbol_c, _symbol_f)
138     _symbol_get = lambda self,x: x or 0
139
140
141 class reference(_column):
142     _type = 'reference'
143     def __init__(self, string, selection, size, **args):
144         _column.__init__(self, string=string, size=size, selection=selection, **args)
145
146
147 class char(_column):
148     _type = 'char'
149
150     def __init__(self, string, size, **args):
151         _column.__init__(self, string=string, size=size, **args)
152         self._symbol_set = (self._symbol_c, self._symbol_set_char)
153
154     # takes a string (encoded in utf8) and returns a string (encoded in utf8)
155     def _symbol_set_char(self, symb):
156         #TODO:
157         # * we need to remove the "symb==False" from the next line BUT
158         #   for now too many things rely on this broken behavior
159         # * the symb==None test should be common to all data types
160         if symb == None or symb == False:
161             return None
162
163         # we need to convert the string to a unicode object to be able
164         # to evaluate its length (and possibly truncate it) reliably
165         u_symb = tools.ustr(symb)
166
167         return u_symb[:self.size].encode('utf8')
168
169
170 class text(_column):
171     _type = 'text'
172
173 import __builtin__
174
175 class float(_column):
176     _type = 'float'
177     _symbol_c = '%s'
178     _symbol_f = lambda x: __builtin__.float(x or 0.0)
179     _symbol_set = (_symbol_c, _symbol_f)
180     _symbol_get = lambda self,x: x or 0.0
181
182     def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
183         _column.__init__(self, string=string, **args)
184         self.digits = digits
185         self.digits_compute = digits_compute
186
187
188     def digits_change(self, cr):
189         if self.digits_compute:
190             t = self.digits_compute(cr)
191             self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
192             self.digits = t
193
194 class date(_column):
195     _type = 'date'
196     @staticmethod
197     def today(*args):
198         """ Returns the current date in a format fit for being a
199         default value to a ``date`` field.
200
201         This method should be provided as is to the _defaults dict, it
202         should not be called.
203         """
204         return DT.date.today().strftime(
205             tools.DEFAULT_SERVER_DATE_FORMAT)
206
207 class datetime(_column):
208     _type = 'datetime'
209     @staticmethod
210     def now(*args):
211         """ Returns the current datetime in a format fit for being a
212         default value to a ``datetime`` field.
213
214         This method should be provided as is to the _defaults dict, it
215         should not be called.
216         """
217         return DT.datetime.now().strftime(
218             tools.DEFAULT_SERVER_DATETIME_FORMAT)
219
220 class time(_column):
221     _type = 'time'
222     @staticmethod
223     def now( *args):
224         """ Returns the current time in a format fit for being a
225         default value to a ``time`` field.
226
227         This method should be proivided as is to the _defaults dict,
228         it should not be called.
229         """
230         return DT.datetime.now().strftime(
231             tools.DEFAULT_SERVER_TIME_FORMAT)
232
233 class binary(_column):
234     _type = 'binary'
235     _symbol_c = '%s'
236     _symbol_f = lambda symb: symb and Binary(symb) or None
237     _symbol_set = (_symbol_c, _symbol_f)
238     _symbol_get = lambda self, x: x and str(x)
239
240     _classic_read = False
241     _prefetch = False
242
243     def __init__(self, string='unknown', filters=None, **args):
244         _column.__init__(self, string=string, **args)
245         self.filters = filters
246
247     def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
248         if not context:
249             context = {}
250         if not values:
251             values = []
252         res = {}
253         for i in ids:
254             val = None
255             for v in values:
256                 if v['id'] == i:
257                     val = v[name]
258                     break
259
260             # If client is requesting only the size of the field, we return it instead
261             # of the content. Presumably a separate request will be done to read the actual
262             # content if it's needed at some point.
263             # TODO: after 6.0 we should consider returning a dict with size and content instead of
264             #       having an implicit convention for the value
265             if val and context.get('bin_size_%s' % name, context.get('bin_size')):
266                 res[i] = tools.human_size(long(val))
267             else:
268                 res[i] = val
269         return res
270
271     get = get_memory
272
273
274 class selection(_column):
275     _type = 'selection'
276
277     def __init__(self, selection, string='unknown', **args):
278         _column.__init__(self, string=string, **args)
279         self.selection = selection
280
281 # ---------------------------------------------------------
282 # Relationals fields
283 # ---------------------------------------------------------
284
285 #
286 # Values: (0, 0,  { fields })    create
287 #         (1, ID, { fields })    update
288 #         (2, ID)                remove (delete)
289 #         (3, ID)                unlink one (target id or target of relation)
290 #         (4, ID)                link
291 #         (5)                    unlink all (only valid for one2many)
292 #
293 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
294 class one2one(_column):
295     _classic_read = False
296     _classic_write = True
297     _type = 'one2one'
298
299     def __init__(self, obj, string='unknown', **args):
300         warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
301         _column.__init__(self, string=string, **args)
302         self._obj = obj
303
304     def set(self, cr, obj_src, id, field, act, user=None, context=None):
305         if not context:
306             context = {}
307         obj = obj_src.pool.get(self._obj)
308         self._table = obj_src.pool.get(self._obj)._table
309         if act[0] == 0:
310             id_new = obj.create(cr, user, act[1])
311             cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
312         else:
313             cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
314             id = cr.fetchone()[0]
315             obj.write(cr, user, [id], act[1], context=context)
316
317     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
318         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
319
320
321 class many2one(_column):
322     _classic_read = False
323     _classic_write = True
324     _type = 'many2one'
325     _symbol_c = '%s'
326     _symbol_f = lambda x: x or None
327     _symbol_set = (_symbol_c, _symbol_f)
328
329     def __init__(self, obj, string='unknown', **args):
330         _column.__init__(self, string=string, **args)
331         self._obj = obj
332
333     def set_memory(self, cr, obj, id, field, values, user=None, context=None):
334         obj.datas.setdefault(id, {})
335         obj.datas[id][field] = values
336
337     def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
338         result = {}
339         for id in ids:
340             result[id] = obj.datas[id].get(name, False)
341         return result
342
343     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
344         if context is None:
345             context = {}
346         if values is None:
347             values = {}
348
349         res = {}
350         for r in values:
351             res[r['id']] = r[name]
352         for id in ids:
353             res.setdefault(id, '')
354         obj = obj.pool.get(self._obj)
355
356         # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
357         # we use uid=1 because the visibility of a many2one field value (just id and name)
358         # must be the access right of the parent form and not the linked object itself.
359         records = dict(obj.name_get(cr, 1, list(set(filter(None, res.values()))), context=context))
360         for id in res:
361             if res[id] in records:
362                 res[id] = (res[id], records[res[id]])
363             else:
364                 res[id] = False
365         return res
366
367     def set(self, cr, obj_src, id, field, values, user=None, context=None):
368         if not context:
369             context = {}
370         obj = obj_src.pool.get(self._obj)
371         self._table = obj_src.pool.get(self._obj)._table
372         if type(values) == type([]):
373             for act in values:
374                 if act[0] == 0:
375                     id_new = obj.create(cr, act[2])
376                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
377                 elif act[0] == 1:
378                     obj.write(cr, [act[1]], act[2], context=context)
379                 elif act[0] == 2:
380                     cr.execute('delete from '+self._table+' where id=%s', (act[1],))
381                 elif act[0] == 3 or act[0] == 5:
382                     cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
383                 elif act[0] == 4:
384                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
385         else:
386             if values:
387                 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
388             else:
389                 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
390
391     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
392         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
393
394
395 class one2many(_column):
396     _classic_read = False
397     _classic_write = False
398     _prefetch = False
399     _type = 'one2many'
400
401     def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
402         _column.__init__(self, string=string, **args)
403         self._obj = obj
404         self._fields_id = fields_id
405         self._limit = limit
406         #one2many can't be used as condition for defaults
407         assert(self.change_default != True)
408
409     def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
410         if context is None:
411             context = {}
412         if self._context:
413             context = context.copy()
414             context.update(self._context)
415         if not values:
416             values = {}
417         res = {}
418         for id in ids:
419             res[id] = []
420         ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
421         for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
422             if r[self._fields_id] in res:
423                 res[r[self._fields_id]].append(r['id'])
424         return res
425
426     def set_memory(self, cr, obj, id, field, values, user=None, context=None):
427         if not context:
428             context = {}
429         if self._context:
430             context = context.copy()
431         context.update(self._context)
432         if not values:
433             return
434         obj = obj.pool.get(self._obj)
435         for act in values:
436             if act[0] == 0:
437                 act[2][self._fields_id] = id
438                 obj.create(cr, user, act[2], context=context)
439             elif act[0] == 1:
440                 obj.write(cr, user, [act[1]], act[2], context=context)
441             elif act[0] == 2:
442                 obj.unlink(cr, user, [act[1]], context=context)
443             elif act[0] == 3:
444                 obj.datas[act[1]][self._fields_id] = False
445             elif act[0] == 4:
446                 obj.datas[act[1]][self._fields_id] = id
447             elif act[0] == 5:
448                 for o in obj.datas.values():
449                     if o[self._fields_id] == id:
450                         o[self._fields_id] = False
451             elif act[0] == 6:
452                 for id2 in (act[2] or []):
453                     obj.datas[id2][self._fields_id] = id
454
455     def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
456         raise _('Not Implemented')
457
458     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
459         if context is None:
460             context = {}
461         if self._context:
462             context = context.copy()
463         context.update(self._context)
464         if values is None:
465             values = {}
466
467         res = {}
468         for id in ids:
469             res[id] = []
470
471         ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
472         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
473             if r[self._fields_id] in res:
474                 res[r[self._fields_id]].append(r['id'])
475         return res
476
477     def set(self, cr, obj, id, field, values, user=None, context=None):
478         result = []
479         if not context:
480             context = {}
481         if self._context:
482             context = context.copy()
483         context.update(self._context)
484         context['no_store_function'] = True
485         if not values:
486             return
487         _table = obj.pool.get(self._obj)._table
488         obj = obj.pool.get(self._obj)
489         for act in values:
490             if act[0] == 0:
491                 act[2][self._fields_id] = id
492                 id_new = obj.create(cr, user, act[2], context=context)
493                 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
494             elif act[0] == 1:
495                 obj.write(cr, user, [act[1]], act[2], context=context)
496             elif act[0] == 2:
497                 obj.unlink(cr, user, [act[1]], context=context)
498             elif act[0] == 3:
499                 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
500             elif act[0] == 4:
501                 cr.execute('update '+_table+' set '+self._fields_id+'=%s where id=%s', (id, act[1]))
502             elif act[0] == 5:
503                 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
504             elif act[0] == 6:
505                 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
506                 ids2 = act[2] or [0]
507                 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
508                 ids3 = map(lambda x:x[0], cr.fetchall())
509                 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
510         return result
511
512     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
513         return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
514
515
516 #
517 # Values: (0, 0,  { fields })    create
518 #         (1, ID, { fields })    update (write fields to ID)
519 #         (2, ID)                remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
520 #         (3, ID)                unlink (delete the relationship between the two objects but does not delete ID)
521 #         (4, ID)                link (add a relationship)
522 #         (5, ID)                unlink all
523 #         (6, ?, ids)            set a list of links
524 #
525 class many2many(_column):
526     _classic_read = False
527     _classic_write = False
528     _prefetch = False
529     _type = 'many2many'
530     def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args):
531         _column.__init__(self, string=string, **args)
532         self._obj = obj
533         if '.' in rel:
534             raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
535                 'You used %s, which is not a valid SQL table name.')% (string,rel))
536         self._rel = rel
537         self._id1 = id1
538         self._id2 = id2
539         self._limit = limit
540
541     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
542         if not context:
543             context = {}
544         if not values:
545             values = {}
546         res = {}
547         if not ids:
548             return res
549         for id in ids:
550             res[id] = []
551         if offset:
552             warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
553                       DeprecationWarning, stacklevel=2)
554         obj = obj.pool.get(self._obj)
555
556         # static domains are lists, and are evaluated both here and on client-side, while string
557         # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
558         # FIXME: make this distinction explicit in API!
559         domain = isinstance(self._domain, list) and self._domain or []
560
561         wquery = obj._where_calc(cr, user, domain, context=context)
562         obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
563         from_c, where_c, where_params = wquery.get_sql()
564         if where_c:
565             where_c = ' AND ' + where_c
566
567         if offset or self._limit:
568             order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
569         else:
570             order_by = ''
571
572         limit_str = ''
573         if self._limit is not None:
574             limit_str = ' LIMIT %d' % self._limit
575
576         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
577                    FROM %(rel)s, %(from_c)s \
578                   WHERE %(rel)s.%(id1)s IN %%s \
579                     AND %(rel)s.%(id2)s = %(tbl)s.id \
580                  %(where_c)s  \
581                  %(order_by)s \
582                  %(limit)s \
583                  OFFSET %(offset)d' \
584             % {'rel': self._rel,
585                'from_c': from_c,
586                'tbl': obj._table,
587                'id1': self._id1,
588                'id2': self._id2,
589                'where_c': where_c,
590                'limit': limit_str,
591                'order_by': order_by,
592                'offset': offset,
593               }
594         cr.execute(query, [tuple(ids),] + where_params)
595         for r in cr.fetchall():
596             res[r[1]].append(r[0])
597         return res
598
599     def set(self, cr, obj, id, name, values, user=None, context=None):
600         if not context:
601             context = {}
602         if not values:
603             return
604         obj = obj.pool.get(self._obj)
605         for act in values:
606             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
607                 continue
608             if act[0] == 0:
609                 idnew = obj.create(cr, user, act[2])
610                 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, idnew))
611             elif act[0] == 1:
612                 obj.write(cr, user, [act[1]], act[2], context=context)
613             elif act[0] == 2:
614                 obj.unlink(cr, user, [act[1]], context=context)
615             elif act[0] == 3:
616                 cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%s and '+ self._id2 + '=%s', (id, act[1]))
617             elif act[0] == 4:
618                 # following queries are in the same transaction - so should be relatively safe
619                 cr.execute('SELECT 1 FROM '+self._rel+' WHERE '+self._id1+' = %s and '+self._id2+' = %s', (id, act[1]))
620                 if not cr.fetchone():
621                     cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, act[1]))
622             elif act[0] == 5:
623                 cr.execute('update '+self._rel+' set '+self._id2+'=null where '+self._id2+'=%s', (id,))
624             elif act[0] == 6:
625
626                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
627                 if d1:
628                     d1 = ' and ' + ' and '.join(d1)
629                 else:
630                     d1 = ''
631                 cr.execute('delete from '+self._rel+' where '+self._id1+'=%s AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+','.join(tables)+' WHERE '+self._rel+'.'+self._id1+'=%s AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2)
632
633                 for act_nbr in act[2]:
634                     cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s, %s)', (id, act_nbr))
635
636     #
637     # TODO: use a name_search
638     #
639     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
640         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
641
642     def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
643         result = {}
644         for id in ids:
645             result[id] = obj.datas[id].get(name, [])
646         return result
647
648     def set_memory(self, cr, obj, id, name, values, user=None, context=None):
649         if not values:
650             return
651         for act in values:
652             # TODO: use constants instead of these magic numbers
653             if act[0] == 0:
654                 raise _('Not Implemented')
655             elif act[0] == 1:
656                 raise _('Not Implemented')
657             elif act[0] == 2:
658                 raise _('Not Implemented')
659             elif act[0] == 3:
660                 raise _('Not Implemented')
661             elif act[0] == 4:
662                 raise _('Not Implemented')
663             elif act[0] == 5:
664                 raise _('Not Implemented')
665             elif act[0] == 6:
666                 obj.datas[id][name] = act[2]
667
668
669 def get_nice_size(a):
670     (x,y) = a
671     if isinstance(y, (int,long)):
672         size = y
673     elif y:
674         size = len(y)
675     else:
676         size = 0
677     return (x, tools.human_size(size))
678
679 def sanitize_binary_value(dict_item):
680     # binary fields should be 7-bit ASCII base64-encoded data,
681     # but we do additional sanity checks to make sure the values
682     # will are not something else that won't pass via xmlrpc
683     index, value = dict_item
684     if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
685         # these builtin types are meant to pass untouched
686         return index, value
687
688     # For all other cases, handle the value as a binary string:
689     # it could be a 7-bit ASCII string (e.g base64 data), but also
690     # any 8-bit content from files, with byte values that cannot
691     # be passed inside XML!
692     # See for more info:
693     #  - http://bugs.python.org/issue10066
694     #  - http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
695     #
696     # One solution is to convert the byte-string to unicode,
697     # so it gets serialized as utf-8 encoded data (always valid XML)
698     # If invalid XML byte values were present, tools.ustr() uses
699     # the Latin-1 codec as fallback, which converts any 8-bit
700     # byte value, resulting in valid utf-8-encoded bytes
701     # in the end:
702     #  >>> unicode('\xe1','latin1').encode('utf8') == '\xc3\xa1'
703     # Note: when this happens, decoding on the other endpoint
704     # is not likely to produce the expected output, but this is
705     # just a safety mechanism (in these cases base64 data or
706     # xmlrpc.Binary values should be used instead
707     return index, tools.ustr(value)
708
709
710 # ---------------------------------------------------------
711 # Function fields
712 # ---------------------------------------------------------
713 class function(_column):
714     _classic_read = False
715     _classic_write = False
716     _prefetch = False
717     _type = 'function'
718     _properties = True
719
720 #
721 # multi: compute several fields in one call
722 #
723     def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False, store=False, multi=False, **args):
724         _column.__init__(self, **args)
725         self._obj = obj
726         self._method = method
727         self._fnct = fnct
728         self._fnct_inv = fnct_inv
729         self._arg = arg
730         self._multi = multi
731         if 'relation' in args:
732             self._obj = args['relation']
733
734         self.digits = args.get('digits', (16,2))
735         self.digits_compute = args.get('digits_compute', None)
736
737         self._fnct_inv_arg = fnct_inv_arg
738         if not fnct_inv:
739             self.readonly = 1
740         self._type = type
741         self._fnct_search = fnct_search
742         self.store = store
743
744         if not fnct_search and not store:
745             self.selectable = False
746
747         if store:
748             if self._type != 'many2one':
749                 # m2o fields need to return tuples with name_get, not just foreign keys
750                 self._classic_read = True
751             self._classic_write = True
752             if type=='binary':
753                 self._symbol_get=lambda x:x and str(x)
754
755         if type == 'float':
756             self._symbol_c = float._symbol_c
757             self._symbol_f = float._symbol_f
758             self._symbol_set = float._symbol_set
759
760         if type == 'boolean':
761             self._symbol_c = boolean._symbol_c
762             self._symbol_f = boolean._symbol_f
763             self._symbol_set = boolean._symbol_set
764
765     def digits_change(self, cr):
766         if self.digits_compute:
767             t = self.digits_compute(cr)
768             self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
769             self.digits = t
770
771
772     def search(self, cr, uid, obj, name, args, context=None):
773         if not self._fnct_search:
774             #CHECKME: should raise an exception
775             return []
776         return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
777
778     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
779         if context is None:
780             context = {}
781         if values is None:
782             values = {}
783         res = {}
784         if self._method:
785             res = self._fnct(obj, cr, user, ids, name, self._arg, context)
786         else:
787             res = self._fnct(cr, obj._table, ids, name, self._arg, context)
788
789         if self._type == "many2one" :
790             # Filtering only integer/long values if passed
791             res_ids = [x for x in res.values() if x and isinstance(x, (int,long))]
792
793             if res_ids:
794                 obj_model = obj.pool.get(self._obj)
795                 dict_names = dict(obj_model.name_get(cr, user, res_ids, context))
796                 for r in res.keys():
797                     if res[r] and res[r] in dict_names:
798                         res[r] = (res[r], dict_names[res[r]])
799
800         if self._type == 'binary':
801             if context.get('bin_size', False):
802                 # client requests only the size of binary fields
803                 res = dict(map(get_nice_size, res.items()))
804             else:
805                 res = dict(map(sanitize_binary_value, res.items()))
806
807         if self._type == "integer":
808             for r in res.keys():
809                 # Converting value into string so that it does not affect XML-RPC Limits
810                 if isinstance(res[r],dict): # To treat integer values with _multi attribute
811                     for record in res[r].keys():
812                         res[r][record] = str(res[r][record])
813                 else:
814                     res[r] = str(res[r])
815         return res
816     get_memory = get
817
818     def set(self, cr, obj, id, name, value, user=None, context=None):
819         if not context:
820             context = {}
821         if self._fnct_inv:
822             self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
823     set_memory = set
824
825 # ---------------------------------------------------------
826 # Related fields
827 # ---------------------------------------------------------
828
829 class related(function):
830
831     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
832         self._field_get2(cr, uid, obj, context)
833         i = len(self._arg)-1
834         sarg = name
835         while i>0:
836             if type(sarg) in [type([]), type( (1,) )]:
837                 where = [(self._arg[i], 'in', sarg)]
838             else:
839                 where = [(self._arg[i], '=', sarg)]
840             if domain:
841                 where = map(lambda x: (self._arg[i],x[1], x[2]), domain)
842                 domain = []
843             sarg = obj.pool.get(self._relations[i]['object']).search(cr, uid, where, context=context)
844             i -= 1
845         return [(self._arg[0], 'in', sarg)]
846
847     def _fnct_write(self,obj,cr, uid, ids, field_name, values, args, context=None):
848         self._field_get2(cr, uid, obj, context=context)
849         if type(ids) != type([]):
850             ids=[ids]
851         objlst = obj.browse(cr, uid, ids)
852         for data in objlst:
853             t_id = data.id
854             t_data = data
855             for i in range(len(self.arg)):
856                 if not t_data: break
857                 field_detail = self._relations[i]
858                 if not t_data[self.arg[i]]:
859                     if self._type not in ('one2many', 'many2many'):
860                         t_id = t_data['id']
861                     t_data = False
862                 elif field_detail['type'] in ('one2many', 'many2many'):
863                     if self._type != "many2one":
864                         t_id = t_data.id
865                         t_data = t_data[self.arg[i]][0]
866                     else:
867                         t_data = False
868                 else:
869                     t_id = t_data['id']
870                     t_data = t_data[self.arg[i]]
871             else:
872                 model = obj.pool.get(self._relations[-1]['object'])
873                 model.write(cr, uid, [t_id], {args[-1]: values}, context=context)
874
875     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
876         self._field_get2(cr, uid, obj, context)
877         if not ids: return {}
878         relation = obj._name
879         if self._type in ('one2many', 'many2many'):
880             res = dict([(i, []) for i in ids])
881         else:
882             res = {}.fromkeys(ids, False)
883
884         objlst = obj.browse(cr, 1, ids, context=context)
885         for data in objlst:
886             if not data:
887                 continue
888             t_data = data
889             relation = obj._name
890             for i in range(len(self.arg)):
891                 field_detail = self._relations[i]
892                 relation = field_detail['object']
893                 try:
894                     if not t_data[self.arg[i]]:
895                         t_data = False
896                         break
897                 except:
898                     t_data = False
899                     break
900                 if field_detail['type'] in ('one2many', 'many2many') and i != len(self.arg) - 1:
901                     t_data = t_data[self.arg[i]][0]
902                 elif t_data:
903                     t_data = t_data[self.arg[i]]
904             if type(t_data) == type(objlst[0]):
905                 res[data.id] = t_data.id
906             elif t_data:
907                 res[data.id] = t_data
908         if self._type=='many2one':
909             ids = filter(None, res.values())
910             if ids:
911                 ng = dict(obj.pool.get(self._obj).name_get(cr, 1, ids, context=context))
912                 for r in res:
913                     if res[r]:
914                         res[r] = (res[r], ng[res[r]])
915         elif self._type in ('one2many', 'many2many'):
916             for r in res:
917                 if res[r]:
918                     res[r] = [x.id for x in res[r]]
919         return res
920
921     def __init__(self, *arg, **args):
922         self.arg = arg
923         self._relations = []
924         super(related, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, method=True, fnct_search=self._fnct_search, **args)
925         if self.store is True:
926             # TODO: improve here to change self.store = {...} according to related objects
927             pass
928
929     def _field_get2(self, cr, uid, obj, context=None):
930         if self._relations:
931             return
932         obj_name = obj._name
933         for i in range(len(self._arg)):
934             f = obj.pool.get(obj_name).fields_get(cr, uid, [self._arg[i]], context=context)[self._arg[i]]
935             self._relations.append({
936                 'object': obj_name,
937                 'type': f['type']
938
939             })
940             if f.get('relation',False):
941                 obj_name = f['relation']
942                 self._relations[-1]['relation'] = f['relation']
943
944 # ---------------------------------------------------------
945 # Dummy fields
946 # ---------------------------------------------------------
947
948 class dummy(function):
949     def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
950         return []
951
952     def _fnct_write(self, obj, cr, uid, ids, field_name, values, args, context=None):
953         return False
954
955     def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
956         return {}
957
958     def __init__(self, *arg, **args):
959         self.arg = arg
960         self._relations = []
961         super(dummy, self).__init__(self._fnct_read, arg, self._fnct_write, fnct_inv_arg=arg, method=True, fnct_search=None, **args)
962
963 # ---------------------------------------------------------
964 # Serialized fields
965 # ---------------------------------------------------------
966 class serialized(_column):
967     def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
968         self._serialize_func = serialize_func
969         self._deserialize_func = deserialize_func
970         self._type = type
971         self._symbol_set = (self._symbol_c, self._serialize_func)
972         self._symbol_get = self._deserialize_func
973         super(serialized, self).__init__(string=string, **args)
974
975
976 # TODO: review completly this class for speed improvement
977 class property(function):
978
979     def _get_default(self, obj, cr, uid, prop_name, context=None):
980         return self._get_defaults(obj, cr, uid, [prop_name], context=None)[0][prop_name]
981
982     def _get_defaults(self, obj, cr, uid, prop_name, context=None):
983         prop = obj.pool.get('ir.property')
984         domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)]
985         ids = prop.search(cr, uid, domain, context=context)
986         replaces = {}
987         default_value = {}.fromkeys(prop_name, False)
988         for prop_rec in prop.browse(cr, uid, ids, context=context):
989             if default_value.get(prop_rec.fields_id.name, False):
990                 continue
991             value = prop.get_by_record(cr, uid, prop_rec, context=context) or False
992             default_value[prop_rec.fields_id.name] = value
993             if value and (prop_rec.type == 'many2one'):
994                 replaces.setdefault(value._name, {})
995                 replaces[value._name][value.id] = True
996         return default_value, replaces
997
998     def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
999         prop = obj.pool.get('ir.property')
1000         vids = [obj._name + ',' + str(oid) for oid in  ids]
1001
1002         domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
1003         #domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
1004         if vids:
1005             domain = [('res_id', 'in', vids)] + domain
1006         return prop.search(cr, uid, domain, context=context)
1007
1008     # TODO: to rewrite more clean
1009     def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
1010         if context is None:
1011             context = {}
1012
1013         nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
1014         if nids:
1015             cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
1016
1017         default_val = self._get_default(obj, cr, uid, prop_name, context)
1018
1019         if id_val is not default_val:
1020             def_id = self._field_get(cr, uid, obj._name, prop_name)
1021             company = obj.pool.get('res.company')
1022             cid = company._company_default_get(cr, uid, obj._name, def_id,
1023                                                context=context)
1024             propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1025                                                              context=context)
1026             prop = obj.pool.get('ir.property')
1027             return prop.create(cr, uid, {
1028                 'name': propdef.name,
1029                 'value': id_val,
1030                 'res_id': obj._name+','+str(id),
1031                 'company_id': cid,
1032                 'fields_id': def_id,
1033                 'type': self._type,
1034             }, context=context)
1035         return False
1036
1037
1038     def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
1039         properties = obj.pool.get('ir.property')
1040         domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
1041         domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in  ids])]
1042         nids = properties.search(cr, uid, domain, context=context)
1043         default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context)
1044
1045         res = {}
1046         for id in ids:
1047             res[id] = default_val.copy()
1048
1049         brs = properties.browse(cr, uid, nids, context=context)
1050         for prop in brs:
1051             value = properties.get_by_record(cr, uid, prop, context=context)
1052             avil_id = obj.pool.get(value._name).search(cr, uid, [('id','=',value.id)], context=context)
1053             res[prop.res_id.id][prop.fields_id.name] = (avil_id and value) and value or False
1054             if value and (prop.type == 'many2one'):
1055                 replaces.setdefault(value._name, {})
1056                 replaces[value._name][value.id] = True
1057
1058         for rep in replaces:
1059             nids = obj.pool.get(rep).search(cr, uid, [('id','in',replaces[rep].keys())], context=context)
1060             replaces[rep] = dict(obj.pool.get(rep).name_get(cr, uid, nids, context=context))
1061
1062         for prop in prop_name:
1063             for id in ids:
1064                 if res[id][prop] and hasattr(res[id][prop], '_name'):
1065                     res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False))
1066
1067         return res
1068
1069
1070     def _field_get(self, cr, uid, model_name, prop):
1071         if not self.field_id.get(cr.dbname):
1072             cr.execute('SELECT id \
1073                     FROM ir_model_fields \
1074                     WHERE name=%s AND model=%s', (prop, model_name))
1075             res = cr.fetchone()
1076             self.field_id[cr.dbname] = res and res[0]
1077         return self.field_id[cr.dbname]
1078
1079     def __init__(self, obj_prop, **args):
1080         # TODO remove obj_prop parameter (use many2one type)
1081         self.field_id = {}
1082         function.__init__(self, self._fnct_read, False, self._fnct_write,
1083                           obj_prop, multi='properties', **args)
1084
1085     def restart(self):
1086         self.field_id = {}
1087
1088
1089 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1090