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