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