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