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