eb4a073e3b20dc77886acad3bb151126887cfd29
[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, manual=False, **args):
67         """
68
69         The 'manual' keyword argument specifies if the field is a custom one.
70         It corresponds to the 'state' column in ir_model_fields.
71
72         """
73         if domain is None:
74             domain = []
75         if context is None:
76             context = {}
77         self.states = states or {}
78         self.string = string
79         self.readonly = readonly
80         self.required = required
81         self.size = size
82         self.help = args.get('help', '')
83         self.priority = priority
84         self.change_default = change_default
85         self.ondelete = ondelete
86         self.translate = translate
87         self._domain = domain
88         self._context = context
89         self.write = False
90         self.read = False
91         self.view_load = 0
92         self.select = select
93         self.manual = manual
94         self.selectable = True
95         self.group_operator = args.get('group_operator', False)
96         for a in args:
97             if args[a]:
98                 setattr(self, a, args[a])
99
100     def restart(self):
101         pass
102
103     def set(self, cr, obj, id, name, value, user=None, context=None):
104         cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
105
106     def set_memory(self, cr, obj, id, name, value, user=None, context=None):
107         raise Exception(_('Not implemented set_memory method !'))
108
109     def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
110         raise Exception(_('Not implemented get_memory method !'))
111
112     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
113         raise Exception(_('undefined get method !'))
114
115     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
116         ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context)
117         res = obj.read(cr, uid, ids, [name], context=context)
118         return [x[name] for x in res]
119
120     def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
121         raise Exception(_('Not implemented search_memory method !'))
122
123
124 # ---------------------------------------------------------
125 # Simple fields
126 # ---------------------------------------------------------
127 class boolean(_column):
128     _type = 'boolean'
129     _symbol_c = '%s'
130     _symbol_f = lambda x: x and 'True' or 'False'
131     _symbol_set = (_symbol_c, _symbol_f)
132
133 class integer(_column):
134     _type = 'integer'
135     _symbol_c = '%s'
136     _symbol_f = lambda x: int(x or 0)
137     _symbol_set = (_symbol_c, _symbol_f)
138     _symbol_get = lambda self,x: x or 0
139
140 class integer_big(_column):
141     _type = 'integer_big'
142     # do not reference the _symbol_* of integer class, as that would possibly
143     # unbind the lambda functions
144     _symbol_c = '%s'
145     _symbol_f = lambda x: int(x or 0)
146     _symbol_set = (_symbol_c, _symbol_f)
147     _symbol_get = lambda self,x: x or 0
148
149 class reference(_column):
150     _type = 'reference'
151     def __init__(self, string, selection, size, **args):
152         _column.__init__(self, string=string, size=size, selection=selection, **args)
153
154
155 class char(_column):
156     _type = 'char'
157
158     def __init__(self, string, size, **args):
159         _column.__init__(self, string=string, size=size, **args)
160         self._symbol_set = (self._symbol_c, self._symbol_set_char)
161
162     # takes a string (encoded in utf8) and returns a string (encoded in utf8)
163     def _symbol_set_char(self, symb):
164         #TODO:
165         # * we need to remove the "symb==False" from the next line BUT
166         #   for now too many things rely on this broken behavior
167         # * the symb==None test should be common to all data types
168         if symb == None or symb == False:
169             return None
170
171         # we need to convert the string to a unicode object to be able
172         # to evaluate its length (and possibly truncate it) reliably
173         u_symb = tools.ustr(symb)
174
175         return u_symb[:self.size].encode('utf8')
176
177
178 class text(_column):
179     _type = 'text'
180
181 import __builtin__
182
183 class float(_column):
184     _type = 'float'
185     _symbol_c = '%s'
186     _symbol_f = lambda x: __builtin__.float(x or 0.0)
187     _symbol_set = (_symbol_c, _symbol_f)
188     _symbol_get = lambda self,x: x or 0.0
189
190     def __init__(self, string='unknown', digits=None, digits_compute=None, **args):
191         _column.__init__(self, string=string, **args)
192         self.digits = digits
193         self.digits_compute = digits_compute
194
195
196     def digits_change(self, cr):
197         if self.digits_compute:
198             t = self.digits_compute(cr)
199             self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),))
200             self.digits = t
201
202 class date(_column):
203     _type = 'date'
204     @staticmethod
205     def today(*args):
206         """ Returns the current date in a format fit for being a
207         default value to a ``date`` field.
208
209         This method should be provided as is to the _defaults dict, it
210         should not be called.
211         """
212         return DT.date.today().strftime(
213             tools.DEFAULT_SERVER_DATE_FORMAT)
214
215 class datetime(_column):
216     _type = 'datetime'
217     @staticmethod
218     def now(*args):
219         """ Returns the current datetime in a format fit for being a
220         default value to a ``datetime`` field.
221
222         This method should be provided as is to the _defaults dict, it
223         should not be called.
224         """
225         return DT.datetime.now().strftime(
226             tools.DEFAULT_SERVER_DATETIME_FORMAT)
227
228 class time(_column):
229     _type = 'time'
230     @staticmethod
231     def now( *args):
232         """ Returns the current time in a format fit for being a
233         default value to a ``time`` field.
234
235         This method should be proivided as is to the _defaults dict,
236         it should not be called.
237         """
238         return DT.datetime.now().strftime(
239             tools.DEFAULT_SERVER_TIME_FORMAT)
240
241 class binary(_column):
242     _type = 'binary'
243     _symbol_c = '%s'
244     _symbol_f = lambda symb: symb and Binary(symb) or None
245     _symbol_set = (_symbol_c, _symbol_f)
246     _symbol_get = lambda self, x: x and str(x)
247
248     _classic_read = False
249     _prefetch = False
250
251     def __init__(self, string='unknown', filters=None, **args):
252         _column.__init__(self, string=string, **args)
253         self.filters = filters
254
255     def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
256         if not context:
257             context = {}
258         if not values:
259             values = []
260         res = {}
261         for i in ids:
262             val = None
263             for v in values:
264                 if v['id'] == i:
265                     val = v[name]
266                     break
267
268             # If client is requesting only the size of the field, we return it instead
269             # of the content. Presumably a separate request will be done to read the actual
270             # content if it's needed at some point.
271             # TODO: after 6.0 we should consider returning a dict with size and content instead of
272             #       having an implicit convention for the value
273             if val and context.get('bin_size_%s' % name, context.get('bin_size')):
274                 res[i] = tools.human_size(long(val))
275             else:
276                 res[i] = val
277         return res
278
279     get = get_memory
280
281
282 class selection(_column):
283     _type = 'selection'
284
285     def __init__(self, selection, string='unknown', **args):
286         _column.__init__(self, string=string, **args)
287         self.selection = selection
288
289 # ---------------------------------------------------------
290 # Relationals fields
291 # ---------------------------------------------------------
292
293 #
294 # Values: (0, 0,  { fields })    create
295 #         (1, ID, { fields })    update
296 #         (2, ID)                remove (delete)
297 #         (3, ID)                unlink one (target id or target of relation)
298 #         (4, ID)                link
299 #         (5)                    unlink all (only valid for one2many)
300 #
301 #CHECKME: dans la pratique c'est quoi la syntaxe utilisee pour le 5? (5) ou (5, 0)?
302 class one2one(_column):
303     _classic_read = False
304     _classic_write = True
305     _type = 'one2one'
306
307     def __init__(self, obj, string='unknown', **args):
308         warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
309         _column.__init__(self, string=string, **args)
310         self._obj = obj
311
312     def set(self, cr, obj_src, id, field, act, user=None, context=None):
313         if not context:
314             context = {}
315         obj = obj_src.pool.get(self._obj)
316         self._table = obj_src.pool.get(self._obj)._table
317         if act[0] == 0:
318             id_new = obj.create(cr, user, act[1])
319             cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
320         else:
321             cr.execute('select '+field+' from '+obj_src._table+' where id=%s', (act[0],))
322             id = cr.fetchone()[0]
323             obj.write(cr, user, [id], act[1], context=context)
324
325     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
326         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
327
328
329 class many2one(_column):
330     _classic_read = False
331     _classic_write = True
332     _type = 'many2one'
333     _symbol_c = '%s'
334     _symbol_f = lambda x: x or None
335     _symbol_set = (_symbol_c, _symbol_f)
336
337     def __init__(self, obj, string='unknown', **args):
338         _column.__init__(self, string=string, **args)
339         self._obj = obj
340
341     def set_memory(self, cr, obj, id, field, values, user=None, context=None):
342         obj.datas.setdefault(id, {})
343         obj.datas[id][field] = values
344
345     def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
346         result = {}
347         for id in ids:
348             result[id] = obj.datas[id].get(name, False)
349
350         # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
351         # we use uid=1 because the visibility of a many2one field value (just id and name)
352         # must be the access right of the parent form and not the linked object itself.
353         obj = obj.pool.get(self._obj)
354         records = dict(obj.name_get(cr, 1,
355                                     list(set([x for x in result.values() if x and isinstance(x, (int,long))])),
356                                     context=context))
357         for id in ids:
358             if result[id] in records:
359                 result[id] = (result[id], records[result[id]])
360             else:
361                 result[id] = False
362
363         return result
364
365     def get(self, cr, obj, ids, name, user=None, context=None, values=None):
366         if context is None:
367             context = {}
368         if values is None:
369             values = {}
370
371         res = {}
372         for r in values:
373             res[r['id']] = r[name]
374         for id in ids:
375             res.setdefault(id, '')
376         obj = obj.pool.get(self._obj)
377
378         # build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
379         # we use uid=1 because the visibility of a many2one field value (just id and name)
380         # must be the access right of the parent form and not the linked object itself.
381         records = dict(obj.name_get(cr, 1,
382                                     list(set([x for x in res.values() if isinstance(x, (int,long))])),
383                                     context=context))
384         for id in res:
385             if res[id] in records:
386                 res[id] = (res[id], records[res[id]])
387             else:
388                 res[id] = False
389         return res
390
391     def set(self, cr, obj_src, id, field, values, user=None, context=None):
392         if not context:
393             context = {}
394         obj = obj_src.pool.get(self._obj)
395         self._table = obj_src.pool.get(self._obj)._table
396         if type(values) == type([]):
397             for act in values:
398                 if act[0] == 0:
399                     id_new = obj.create(cr, act[2])
400                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (id_new, id))
401                 elif act[0] == 1:
402                     obj.write(cr, [act[1]], act[2], context=context)
403                 elif act[0] == 2:
404                     cr.execute('delete from '+self._table+' where id=%s', (act[1],))
405                 elif act[0] == 3 or act[0] == 5:
406                     cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
407                 elif act[0] == 4:
408                     cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (act[1], id))
409         else:
410             if values:
411                 cr.execute('update '+obj_src._table+' set '+field+'=%s where id=%s', (values, id))
412             else:
413                 cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,))
414
415     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
416         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context)
417
418
419 class one2many(_column):
420     _classic_read = False
421     _classic_write = False
422     _prefetch = False
423     _type = 'one2many'
424
425     def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
426         _column.__init__(self, string=string, **args)
427         self._obj = obj
428         self._fields_id = fields_id
429         self._limit = limit
430         #one2many can't be used as condition for defaults
431         assert(self.change_default != True)
432
433     def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
434         if context is None:
435             context = {}
436         if self._context:
437             context = context.copy()
438             context.update(self._context)
439         if not values:
440             values = {}
441         res = {}
442         for id in ids:
443             res[id] = []
444         ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
445         for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
446             if r[self._fields_id] in res:
447                 res[r[self._fields_id]].append(r['id'])
448         return res
449
450     def set_memory(self, cr, obj, id, field, values, user=None, context=None):
451         if not context:
452             context = {}
453         if self._context:
454             context = context.copy()
455         context.update(self._context)
456         if not values:
457             return
458         obj = obj.pool.get(self._obj)
459         for act in values:
460             if act[0] == 0:
461                 act[2][self._fields_id] = id
462                 obj.create(cr, user, act[2], context=context)
463             elif act[0] == 1:
464                 obj.write(cr, user, [act[1]], act[2], context=context)
465             elif act[0] == 2:
466                 obj.unlink(cr, user, [act[1]], context=context)
467             elif act[0] == 3:
468                 obj.datas[act[1]][self._fields_id] = False
469             elif act[0] == 4:
470                 obj.datas[act[1]][self._fields_id] = id
471             elif act[0] == 5:
472                 for o in obj.datas.values():
473                     if o[self._fields_id] == id:
474                         o[self._fields_id] = False
475             elif act[0] == 6:
476                 for id2 in (act[2] or []):
477                     obj.datas[id2][self._fields_id] = id
478
479     def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
480         raise _('Not Implemented')
481
482     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
483         if context is None:
484             context = {}
485         if self._context:
486             context = context.copy()
487         context.update(self._context)
488         if values is None:
489             values = {}
490
491         res = {}
492         for id in ids:
493             res[id] = []
494
495         ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
496         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
497             if r[self._fields_id] in res:
498                 res[r[self._fields_id]].append(r['id'])
499         return res
500
501     def set(self, cr, obj, id, field, values, user=None, context=None):
502         result = []
503         if not context:
504             context = {}
505         if self._context:
506             context = context.copy()
507         context.update(self._context)
508         context['no_store_function'] = True
509         if not values:
510             return
511         _table = obj.pool.get(self._obj)._table
512         obj = obj.pool.get(self._obj)
513         for act in values:
514             if act[0] == 0:
515                 act[2][self._fields_id] = id
516                 id_new = obj.create(cr, user, act[2], context=context)
517                 result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
518             elif act[0] == 1:
519                 obj.write(cr, user, [act[1]], act[2], context=context)
520             elif act[0] == 2:
521                 obj.unlink(cr, user, [act[1]], context=context)
522             elif act[0] == 3:
523                 cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
524             elif act[0] == 4:
525                 # Must use write() to recompute parent_store structure if needed
526                 obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
527             elif act[0] == 5:
528                 cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%s', (id,))
529             elif act[0] == 6:
530                 # Must use write() to recompute parent_store structure if needed
531                 obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
532                 ids2 = act[2] or [0]
533                 cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
534                 ids3 = map(lambda x:x[0], cr.fetchall())
535                 obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
536         return result
537
538     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
539         return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
540
541
542 #
543 # Values: (0, 0,  { fields })    create
544 #         (1, ID, { fields })    update (write fields to ID)
545 #         (2, ID)                remove (calls unlink on ID, that will also delete the relationship because of the ondelete)
546 #         (3, ID)                unlink (delete the relationship between the two objects but does not delete ID)
547 #         (4, ID)                link (add a relationship)
548 #         (5, ID)                unlink all
549 #         (6, ?, ids)            set a list of links
550 #
551 class many2many(_column):
552     _classic_read = False
553     _classic_write = False
554     _prefetch = False
555     _type = 'many2many'
556     def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args):
557         _column.__init__(self, string=string, **args)
558         self._obj = obj
559         if '.' in rel:
560             raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
561                 'You used %s, which is not a valid SQL table name.')% (string,rel))
562         self._rel = rel
563         self._id1 = id1
564         self._id2 = id2
565         self._limit = limit
566
567     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
568         if not context:
569             context = {}
570         if not values:
571             values = {}
572         res = {}
573         if not ids:
574             return res
575         for id in ids:
576             res[id] = []
577         if offset:
578             warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
579                       DeprecationWarning, stacklevel=2)
580         obj = obj.pool.get(self._obj)
581
582         # static domains are lists, and are evaluated both here and on client-side, while string
583         # domains supposed by dynamic and evaluated on client-side only (thus ignored here)
584         # FIXME: make this distinction explicit in API!
585         domain = isinstance(self._domain, list) and self._domain or []
586
587         wquery = obj._where_calc(cr, user, domain, context=context)
588         obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
589         from_c, where_c, where_params = wquery.get_sql()
590         if where_c:
591             where_c = ' AND ' + where_c
592
593         if offset or self._limit:
594             order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
595         else:
596             order_by = ''
597
598         limit_str = ''
599         if self._limit is not None:
600             limit_str = ' LIMIT %d' % self._limit
601
602         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
603                    FROM %(rel)s, %(from_c)s \
604                   WHERE %(rel)s.%(id1)s IN %%s \
605                     AND %(rel)s.%(id2)s = %(tbl)s.id \
606                  %(where_c)s  \
607                  %(order_by)s \
608                  %(limit)s \
609                  OFFSET %(offset)d' \
610             % {'rel': self._rel,
611                'from_c': from_c,
612                'tbl': obj._table,
613                'id1': self._id1,
614                'id2': self._id2,
615                'where_c': where_c,
616                'limit': limit_str,
617                'order_by': order_by,
618                'offset': offset,
619               }
620         cr.execute(query, [tuple(ids),] + where_params)
621         for r in cr.fetchall():
622             res[r[1]].append(r[0])
623         return res
624
625     def set(self, cr, obj, id, name, values, user=None, context=None):
626         if not context:
627             context = {}
628         if not values:
629             return
630         obj = obj.pool.get(self._obj)
631         for act in values:
632             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
633                 continue
634             if act[0] == 0:
635                 idnew = obj.create(cr, user, act[2])
636                 cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, idnew))
637             elif act[0] == 1:
638                 obj.write(cr, user, [act[1]], act[2], context=context)
639             elif act[0] == 2:
640                 obj.unlink(cr, user, [act[1]], context=context)
641             elif act[0] == 3:
642                 cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%s and '+ self._id2 + '=%s', (id, act[1]))
643             elif act[0] == 4:
644                 # following queries are in the same transaction - so should be relatively safe
645                 cr.execute('SELECT 1 FROM '+self._rel+' WHERE '+self._id1+' = %s and '+self._id2+' = %s', (id, act[1]))
646                 if not cr.fetchone():
647                     cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, act[1]))
648             elif act[0] == 5:
649                 cr.execute('update '+self._rel+' set '+self._id2+'=null where '+self._id2+'=%s', (id,))
650             elif act[0] == 6:
651
652                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
653                 if d1:
654                     d1 = ' and ' + ' and '.join(d1)
655                 else:
656                     d1 = ''
657                 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)
658
659                 for act_nbr in act[2]:
660                     cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s, %s)', (id, act_nbr))
661
662     #
663     # TODO: use a name_search
664     #
665     def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
666         return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
667
668     def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
669         result = {}
670         for id in ids:
671             result[id] = obj.datas[id].get(name, [])
672         return result
673
674     def set_memory(self, cr, obj, id, name, values, user=None, context=None):
675         if not values:
676             return
677         for act in values:
678             # TODO: use constants instead of these magic numbers
679             if act[0] == 0:
680                 raise _('Not Implemented')
681             elif act[0] == 1:
682                 raise _('Not Implemented')
683             elif act[0] == 2:
684                 raise _('Not Implemented')
685             elif act[0] == 3:
686                 raise _('Not Implemented')
687             elif act[0] == 4:
688                 raise _('Not Implemented')
689             elif act[0] == 5:
690                 raise _('Not Implemented')
691             elif act[0] == 6:
692                 obj.datas[id][name] = act[2]
693
694
695 def get_nice_size(value):
696     size = 0
697     if isinstance(value, (int,long)):
698         size = value
699     elif value: # this is supposed to be a string
700         size = len(value)
701     return tools.human_size(size)
702
703 def sanitize_binary_value(value):
704     # binary fields should be 7-bit ASCII base64-encoded data,
705     # but we do additional sanity checks to make sure the values
706     # are not something else that won't pass via xmlrpc
707     if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
708         # these builtin types are meant to pass untouched
709         return 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 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 postprocess(self, cr, uid, obj, field, value=None, context=None):
807         if context is None:
808             context = {}
809         result = value
810         field_type = obj._columns[field]._type
811         if field_type == "many2one":
812             # make the result a tuple if it is not already one
813             if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
814                 obj_model = obj.pool.get(obj._columns[field].relation)
815                 dict_names = dict(obj_model.name_get(cr, uid, [value], context))
816                 result = (value, dict_names[value])
817
818         if field_type == 'binary':
819             if context.get('bin_size', False):
820                 # client requests only the size of binary fields
821                 result = get_nice_size(value)
822             else:
823                 result = sanitize_binary_value(value)
824
825         if field_type == "integer":
826             result = tools.ustr(value)
827         return result
828
829     def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
830         result = {}
831         if self._method:
832             result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
833         else:
834             result = self._fnct(cr, obj._table, ids, name, self._arg, context)
835         for id in ids:
836             if self._multi and id in result:
837                 for field, value in result[id].iteritems():
838                     if value:
839                         result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
840             elif result.get(id):
841                 result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
842         return result
843
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
1047         if id_val is not default_val:
1048             def_id = self._field_get(cr, uid, obj._name, prop_name)
1049             company = obj.pool.get('res.company')
1050             cid = company._company_default_get(cr, uid, obj._name, def_id,
1051                                                context=context)
1052             propdef = obj.pool.get('ir.model.fields').browse(cr, uid, def_id,
1053                                                              context=context)
1054             prop = obj.pool.get('ir.property')
1055             return prop.create(cr, uid, {
1056                 'name': propdef.name,
1057                 'value': id_val,
1058                 'res_id': obj._name+','+str(id),
1059                 'company_id': cid,
1060                 'fields_id': def_id,
1061                 'type': self._type,
1062             }, context=context)
1063         return False
1064
1065
1066     def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
1067         properties = obj.pool.get('ir.property')
1068         domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
1069         domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in  ids])]
1070         nids = properties.search(cr, uid, domain, context=context)
1071         default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context)
1072
1073         res = {}
1074         for id in ids:
1075             res[id] = default_val.copy()
1076
1077         brs = properties.browse(cr, uid, nids, context=context)
1078         for prop in brs:
1079             value = properties.get_by_record(cr, uid, prop, context=context)
1080             res[prop.res_id.id][prop.fields_id.name] = value or False
1081             if value and (prop.type == 'many2one'):
1082                 record_exists = obj.pool.get(value._name).exists(cr, uid, value.id)
1083                 if record_exists:
1084                     replaces.setdefault(value._name, {})
1085                     replaces[value._name][value.id] = True
1086                 else:
1087                     res[prop.res_id.id][prop.fields_id.name] = False
1088
1089         for rep in replaces:
1090             nids = obj.pool.get(rep).search(cr, uid, [('id','in',replaces[rep].keys())], context=context)
1091             replaces[rep] = dict(obj.pool.get(rep).name_get(cr, uid, nids, context=context))
1092
1093         for prop in prop_name:
1094             for id in ids:
1095                 if res[id][prop] and hasattr(res[id][prop], '_name'):
1096                     res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False))
1097
1098         return res
1099
1100
1101     def _field_get(self, cr, uid, model_name, prop):
1102         if not self.field_id.get(cr.dbname):
1103             cr.execute('SELECT id \
1104                     FROM ir_model_fields \
1105                     WHERE name=%s AND model=%s', (prop, model_name))
1106             res = cr.fetchone()
1107             self.field_id[cr.dbname] = res and res[0]
1108         return self.field_id[cr.dbname]
1109
1110     def __init__(self, obj_prop, **args):
1111         # TODO remove obj_prop parameter (use many2one type)
1112         self.field_id = {}
1113         function.__init__(self, self._fnct_read, False, self._fnct_write,
1114                           obj_prop, multi='properties', **args)
1115
1116     def restart(self):
1117         self.field_id = {}
1118
1119
1120 def field_to_dict(self, cr, user, context, field):
1121     """ Return a dictionary representation of a field.
1122
1123     The string, help, and selection attributes (if any) are untranslated.  This
1124     representation is the one returned by fields_get() (fields_get() will do
1125     the translation).
1126
1127     """
1128
1129     res = {'type': field._type}
1130     # This additional attributes for M2M and function field is added
1131     # because we need to display tooltip with this additional information
1132     # when client is started in debug mode.
1133     if isinstance(field, function):
1134         res['function'] = field._fnct and field._fnct.func_name or False
1135         res['store'] = field.store
1136         if isinstance(field.store, dict):
1137             res['store'] = str(field.store)
1138         res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
1139         res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
1140         res['fnct_inv_arg'] = field._fnct_inv_arg or False
1141         res['func_obj'] = field._obj or False
1142         res['func_method'] = field._method
1143     if isinstance(field, many2many):
1144         res['related_columns'] = list((field._id1, field._id2))
1145         res['third_table'] = field._rel
1146     for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1147             'change_default', 'translate', 'help', 'select', 'selectable'):
1148         if getattr(field, arg):
1149             res[arg] = getattr(field, arg)
1150     for arg in ('digits', 'invisible', 'filters'):
1151         if getattr(field, arg, None):
1152             res[arg] = getattr(field, arg)
1153
1154     if field.string:
1155         res['string'] = field.string
1156     if field.help:
1157         res['help'] = field.help
1158
1159     if hasattr(field, 'selection'):
1160         if isinstance(field.selection, (tuple, list)):
1161             res['selection'] = field.selection
1162         else:
1163             # call the 'dynamic selection' function
1164             res['selection'] = field.selection(self, cr, user, context)
1165     if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1166         res['relation'] = field._obj
1167         res['domain'] = field._domain
1168         res['context'] = field._context
1169
1170     return res
1171
1172
1173 class column_info(object):
1174     """Struct containing details about an osv column, either one local to
1175        its model, or one inherited via _inherits.
1176
1177        :attr name: name of the column
1178        :attr column: column instance, subclass of osv.fields._column
1179        :attr parent_model: if the column is inherited, name of the model
1180                            that contains it, None for local columns.
1181        :attr parent_column: the name of the column containing the m2o
1182                             relationship to the parent model that contains
1183                             this column, None for local columns.
1184     """
1185     def __init__(self, name, column, parent_model=None, parent_column=None):
1186         self.name = name
1187         self.column = column
1188         self.parent_model = parent_model
1189         self.parent_column = parent_column
1190
1191 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1192