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