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