[FIX] osv_memory: load config params in __init__ to avoid uninitialized values
[odoo/odoo.git] / openerp / osv / orm.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 #
23 # Object relationnal mapping to postgresql module
24 #    . Hierarchical structure
25 #    . Constraints consistency, validations
26 #    . Object meta Data depends on its status
27 #    . Optimised processing by complex query (multiple actions at once)
28 #    . Default fields value
29 #    . Permissions optimisation
30 #    . Persistant object: DB postgresql
31 #    . Datas conversions
32 #    . Multi-level caching system
33 #    . 2 different inheritancies
34 #    . Fields:
35 #         - classicals (varchar, integer, boolean, ...)
36 #         - relations (one2many, many2one, many2many)
37 #         - functions
38 #
39 #
40 import calendar
41 import copy
42 import datetime
43 import logging
44 import warnings
45 import operator
46 import pickle
47 import re
48 import time
49 import traceback
50 import types
51
52 import openerp.netsvc as netsvc
53 from lxml import etree
54 from openerp.tools.config import config
55 from openerp.tools.translate import _
56
57 import fields
58 from query import Query
59 import openerp.tools as tools
60 from openerp.tools.safe_eval import safe_eval as eval
61
62 # List of etree._Element subclasses that we choose to ignore when parsing XML.
63 from openerp.tools import SKIPPED_ELEMENT_TYPES
64
65 regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
66 regex_object_name = re.compile(r'^[a-z0-9_.]+$')
67
68 # Mapping between openerp module names and their osv classes.
69 module_class_list = {}
70
71 def check_object_name(name):
72     """ Check if the given name is a valid openerp object name.
73
74         The _name attribute in osv and osv_memory object is subject to
75         some restrictions. This function returns True or False whether
76         the given name is allowed or not.
77
78         TODO: this is an approximation. The goal in this approximation
79         is to disallow uppercase characters (in some places, we quote
80         table/column names and in other not, which leads to this kind
81         of errors:
82
83             psycopg2.ProgrammingError: relation "xxx" does not exist).
84
85         The same restriction should apply to both osv and osv_memory
86         objects for consistency.
87
88     """
89     if regex_object_name.match(name) is None:
90         return False
91     return True
92
93 def raise_on_invalid_object_name(name):
94     if not check_object_name(name):
95         msg = "The _name attribute %s is not valid." % name
96         logger = netsvc.Logger()
97         logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
98         raise except_orm('ValueError', msg)
99
100 POSTGRES_CONFDELTYPES = {
101     'RESTRICT': 'r',
102     'NO ACTION': 'a',
103     'CASCADE': 'c',
104     'SET NULL': 'n',
105     'SET DEFAULT': 'd',
106 }
107
108 def last_day_of_current_month():
109     today = datetime.date.today()
110     last_day = str(calendar.monthrange(today.year, today.month)[1])
111     return time.strftime('%Y-%m-' + last_day)
112
113 def intersect(la, lb):
114     return filter(lambda x: x in lb, la)
115
116 class except_orm(Exception):
117     def __init__(self, name, value):
118         self.name = name
119         self.value = value
120         self.args = (name, value)
121
122 class BrowseRecordError(Exception):
123     pass
124
125 # Readonly python database object browser
126 class browse_null(object):
127
128     def __init__(self):
129         self.id = False
130
131     def __getitem__(self, name):
132         return None
133
134     def __getattr__(self, name):
135         return None  # XXX: return self ?
136
137     def __int__(self):
138         return False
139
140     def __str__(self):
141         return ''
142
143     def __nonzero__(self):
144         return False
145
146     def __unicode__(self):
147         return u''
148
149
150 #
151 # TODO: execute an object method on browse_record_list
152 #
153 class browse_record_list(list):
154
155     def __init__(self, lst, context=None):
156         if not context:
157             context = {}
158         super(browse_record_list, self).__init__(lst)
159         self.context = context
160
161
162 class browse_record(object):
163     logger = netsvc.Logger()
164
165     def __init__(self, cr, uid, id, table, cache, context=None, list_class=None, fields_process=None):
166         '''
167         table : the object (inherited from orm)
168         context : dictionary with an optional context
169         '''
170         if fields_process is None:
171             fields_process = {}
172         if context is None:
173             context = {}
174         self._list_class = list_class or browse_record_list
175         self._cr = cr
176         self._uid = uid
177         self._id = id
178         self._table = table
179         self._table_name = self._table._name
180         self.__logger = logging.getLogger(
181             'osv.browse_record.' + self._table_name)
182         self._context = context
183         self._fields_process = fields_process
184
185         cache.setdefault(table._name, {})
186         self._data = cache[table._name]
187
188         if not (id and isinstance(id, (int, long,))):
189             raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
190 #        if not table.exists(cr, uid, id, context):
191 #            raise BrowseRecordError(_('Object %s does not exists') % (self,))
192
193         if id not in self._data:
194             self._data[id] = {'id': id}
195
196         self._cache = cache
197
198     def __getitem__(self, name):
199         if name == 'id':
200             return self._id
201
202         if name not in self._data[self._id]:
203             # build the list of fields we will fetch
204
205             # fetch the definition of the field which was asked for
206             if name in self._table._columns:
207                 col = self._table._columns[name]
208             elif name in self._table._inherit_fields:
209                 col = self._table._inherit_fields[name][2]
210             elif hasattr(self._table, str(name)):
211                 attr = getattr(self._table, name)
212
213                 if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
214                     return lambda *args, **argv: attr(self._cr, self._uid, [self._id], *args, **argv)
215                 else:
216                     return attr
217             else:
218                 self.logger.notifyChannel("browse_record", netsvc.LOG_WARNING,
219                     "Field '%s' does not exist in object '%s': \n%s" % (
220                         name, self, ''.join(traceback.format_exc())))
221                 raise KeyError("Field '%s' does not exist in object '%s'" % (
222                     name, self))
223
224             # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
225             if col._prefetch:
226                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
227                 fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
228                 # gen the list of inherited fields
229                 inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
230                 # complete the field list with the inherited fields which are classic or many2one
231                 fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
232             # otherwise we fetch only that field
233             else:
234                 fields_to_fetch = [(name, col)]
235             ids = filter(lambda id: name not in self._data[id], self._data.keys())
236             # read the results
237             field_names = map(lambda x: x[0], fields_to_fetch)
238             field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
239
240             # TODO: improve this, very slow for reports
241             if self._fields_process:
242                 lang = self._context.get('lang', 'en_US') or 'en_US'
243                 lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
244                 if not lang_obj_ids:
245                     raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
246                 lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
247
248                 for field_name, field_column in fields_to_fetch:
249                     if field_column._type in self._fields_process:
250                         for result_line in field_values:
251                             result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
252                             if result_line[field_name]:
253                                 result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
254
255             if not field_values:
256                 # Where did those ids come from? Perhaps old entries in ir_model_dat?
257                 self.__logger.warn("No field_values found for ids %s in %s", ids, self)
258                 raise KeyError('Field %s not found in %s'%(name, self))
259             # create browse records for 'remote' objects
260             for result_line in field_values:
261                 new_data = {}
262                 for field_name, field_column in fields_to_fetch:
263                     if field_column._type in ('many2one', 'one2one'):
264                         if result_line[field_name]:
265                             obj = self._table.pool.get(field_column._obj)
266                             if isinstance(result_line[field_name], (list, tuple)):
267                                 value = result_line[field_name][0]
268                             else:
269                                 value = result_line[field_name]
270                             if value:
271                                 # FIXME: this happen when a _inherits object
272                                 #        overwrite a field of it parent. Need
273                                 #        testing to be sure we got the right
274                                 #        object and not the parent one.
275                                 if not isinstance(value, browse_record):
276                                     if obj is None:
277                                         # In some cases the target model is not available yet, so we must ignore it,
278                                         # which is safe in most cases, this value will just be loaded later when needed.
279                                         # This situation can be caused by custom fields that connect objects with m2o without
280                                         # respecting module dependencies, causing relationships to be connected to soon when
281                                         # the target is not loaded yet.
282                                         continue
283                                     new_data[field_name] = browse_record(self._cr,
284                                         self._uid, value, obj, self._cache,
285                                         context=self._context,
286                                         list_class=self._list_class,
287                                         fields_process=self._fields_process)
288                                 else:
289                                     new_data[field_name] = value
290                             else:
291                                 new_data[field_name] = browse_null()
292                         else:
293                             new_data[field_name] = browse_null()
294                     elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
295                         new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(field_column._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
296                     elif field_column._type in ('reference'):
297                         if result_line[field_name]:
298                             if isinstance(result_line[field_name], browse_record):
299                                 new_data[field_name] = result_line[field_name]
300                             else:
301                                 ref_obj, ref_id = result_line[field_name].split(',')
302                                 ref_id = long(ref_id)
303                                 if ref_id:
304                                     obj = self._table.pool.get(ref_obj)
305                                     new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
306                                 else:
307                                     new_data[field_name] = browse_null()
308                         else:
309                             new_data[field_name] = browse_null()
310                     else:
311                         new_data[field_name] = result_line[field_name]
312                 self._data[result_line['id']].update(new_data)
313
314         if not name in self._data[self._id]:
315             # How did this happen? Could be a missing model due to custom fields used too soon, see above.
316             self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
317                     "Fields to fetch: %s, Field values: %s"%(field_names, field_values))
318             self.logger.notifyChannel("browse_record", netsvc.LOG_ERROR,
319                     "Cached: %s, Table: %s"%(self._data[self._id], self._table))
320             raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
321         return self._data[self._id][name]
322
323     def __getattr__(self, name):
324         try:
325             return self[name]
326         except KeyError, e:
327             raise AttributeError(e)
328
329     def __contains__(self, name):
330         return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
331
332     def __hasattr__(self, name):
333         return name in self
334
335     def __int__(self):
336         return self._id
337
338     def __str__(self):
339         return "browse_record(%s, %d)" % (self._table_name, self._id)
340
341     def __eq__(self, other):
342         if not isinstance(other, browse_record):
343             return False
344         return (self._table_name, self._id) == (other._table_name, other._id)
345
346     def __ne__(self, other):
347         if not isinstance(other, browse_record):
348             return True
349         return (self._table_name, self._id) != (other._table_name, other._id)
350
351     # we need to define __unicode__ even though we've already defined __str__
352     # because we have overridden __getattr__
353     def __unicode__(self):
354         return unicode(str(self))
355
356     def __hash__(self):
357         return hash((self._table_name, self._id))
358
359     __repr__ = __str__
360
361
362 def get_pg_type(f):
363     '''
364     returns a tuple
365     (type returned by postgres when the column was created, type expression to create the column)
366     '''
367
368     type_dict = {
369             fields.boolean: 'bool',
370             fields.integer: 'int4',
371             fields.integer_big: 'int8',
372             fields.text: 'text',
373             fields.date: 'date',
374             fields.time: 'time',
375             fields.datetime: 'timestamp',
376             fields.binary: 'bytea',
377             fields.many2one: 'int4',
378             }
379     if type(f) in type_dict:
380         f_type = (type_dict[type(f)], type_dict[type(f)])
381     elif isinstance(f, fields.float):
382         if f.digits:
383             f_type = ('numeric', 'NUMERIC')
384         else:
385             f_type = ('float8', 'DOUBLE PRECISION')
386     elif isinstance(f, (fields.char, fields.reference)):
387         f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
388     elif isinstance(f, fields.selection):
389         if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
390             f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
391         elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
392             f_size = -1
393         else:
394             f_size = getattr(f, 'size', None) or 16
395
396         if f_size == -1:
397             f_type = ('int4', 'INTEGER')
398         else:
399             f_type = ('varchar', 'VARCHAR(%d)' % f_size)
400     elif isinstance(f, fields.function) and eval('fields.'+(f._type), globals()) in type_dict:
401         t = eval('fields.'+(f._type), globals())
402         f_type = (type_dict[t], type_dict[t])
403     elif isinstance(f, fields.function) and f._type == 'float':
404         if f.digits:
405             f_type = ('numeric', 'NUMERIC')
406         else:
407             f_type = ('float8', 'DOUBLE PRECISION')
408     elif isinstance(f, fields.function) and f._type == 'selection':
409         f_type = ('text', 'text')
410     elif isinstance(f, fields.function) and f._type == 'char':
411         f_type = ('varchar', 'VARCHAR(%d)' % (f.size))
412     else:
413         logger = netsvc.Logger()
414         logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f)))
415         f_type = None
416     return f_type
417
418
419 class orm_template(object):
420     """ Base class for OpenERP models.
421
422     OpenERP models are created by inheriting from this class (although
423     not directly; more specifically by inheriting from osv or
424     osv_memory). The constructor is called once, usually directly
425     after the class definition, e.g.:
426
427         class user(osv):
428             ...
429         user()
430
431     The system will later instanciate the class once per database (on
432     which the class' module is installed).
433
434     """
435     _name = None
436     _columns = {}
437     _constraints = []
438     _defaults = {}
439     _rec_name = 'name'
440     _parent_name = 'parent_id'
441     _parent_store = False
442     _parent_order = False
443     _date_name = 'date'
444     _order = 'id'
445     _sequence = None
446     _description = None
447     _inherits = {}
448     _table = None
449     _invalids = set()
450     _log_create = False
451
452     CONCURRENCY_CHECK_FIELD = '__last_update'
453     def log(self, cr, uid, id, message, secondary=False, context=None):
454         return self.pool.get('res.log').create(cr, uid,
455                 {
456                     'name': message,
457                     'res_model': self._name,
458                     'secondary': secondary,
459                     'res_id': id,
460                 },
461                 context=context
462         )
463
464     def view_init(self, cr, uid, fields_list, context=None):
465         """Override this method to do specific things when a view on the object is opened."""
466         pass
467
468     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
469         raise NotImplementedError(_('The read_group method is not implemented on this object !'))
470
471     def _field_create(self, cr, context=None):
472         """
473
474         Create/update entries in ir_model, ir_model_data, and ir_model_fields.
475
476         - create an entry in ir_model (if there is not already one),
477         - create an entry in ir_model_data (if there is not already one, and if
478           'module' is in the context),
479         - update ir_model_fields with the fields found in _columns
480           (TODO there is some redundancy as _columns is updated from
481           ir_model_fields in __init__).
482
483         """
484         if context is None:
485             context = {}
486         cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
487         if not cr.rowcount:
488             cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
489             model_id = cr.fetchone()[0]
490             cr.execute("INSERT INTO ir_model (id,model, name, info,state) VALUES (%s, %s, %s, %s, %s)", (model_id, self._name, self._description, self.__doc__, 'base'))
491         else:
492             model_id = cr.fetchone()[0]
493         if 'module' in context:
494             name_id = 'model_'+self._name.replace('.', '_')
495             cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
496             if not cr.rowcount:
497                 cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
498                     (name_id, context['module'], 'ir.model', model_id)
499                 )
500
501         cr.commit()
502
503         cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
504         cols = {}
505         for rec in cr.dictfetchall():
506             cols[rec['name']] = rec
507
508         for (k, f) in self._columns.items():
509             vals = {
510                 'model_id': model_id,
511                 'model': self._name,
512                 'name': k,
513                 'field_description': f.string.replace("'", " "),
514                 'ttype': f._type,
515                 'relation': f._obj or '',
516                 'view_load': (f.view_load and 1) or 0,
517                 'select_level': tools.ustr(f.select or 0),
518                 'readonly': (f.readonly and 1) or 0,
519                 'required': (f.required and 1) or 0,
520                 'selectable': (f.selectable and 1) or 0,
521                 'translate': (f.translate and 1) or 0,
522                 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
523             }
524             # When its a custom field,it does not contain f.select
525             if context.get('field_state', 'base') == 'manual':
526                 if context.get('field_name', '') == k:
527                     vals['select_level'] = context.get('select', '0')
528                 #setting value to let the problem NOT occur next time
529                 elif k in cols:
530                     vals['select_level'] = cols[k]['select_level']
531
532             if k not in cols:
533                 cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
534                 id = cr.fetchone()[0]
535                 vals['id'] = id
536                 cr.execute("""INSERT INTO ir_model_fields (
537                     id, model_id, model, name, field_description, ttype,
538                     relation,view_load,state,select_level,relation_field, translate
539                 ) VALUES (
540                     %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
541                 )""", (
542                     id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
543                      vals['relation'], bool(vals['view_load']), 'base',
544                     vals['select_level'], vals['relation_field'], bool(vals['translate'])
545                 ))
546                 if 'module' in context:
547                     name1 = 'field_' + self._table + '_' + k
548                     cr.execute("select name from ir_model_data where name=%s", (name1,))
549                     if cr.fetchone():
550                         name1 = name1 + "_" + str(id)
551                     cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
552                         (name1, context['module'], 'ir.model.fields', id)
553                     )
554             else:
555                 for key, val in vals.items():
556                     if cols[k][key] != vals[key]:
557                         cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
558                         cr.commit()
559                         cr.execute("""UPDATE ir_model_fields SET
560                             model_id=%s, field_description=%s, ttype=%s, relation=%s,
561                             view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s
562                         WHERE
563                             model=%s AND name=%s""", (
564                                 vals['model_id'], vals['field_description'], vals['ttype'],
565                                 vals['relation'], bool(vals['view_load']),
566                                 vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['model'], vals['name']
567                             ))
568                         break
569         cr.commit()
570
571     def _auto_init(self, cr, context=None):
572         raise_on_invalid_object_name(self._name)
573         self._field_create(cr, context=context)
574
575     #
576     # Goal: try to apply inheritance at the instanciation level and
577     #       put objects in the pool var
578     #
579     @classmethod
580     def makeInstance(cls, pool, cr, attributes):
581         """ Instanciate a given model.
582
583         This class method instanciates the class of some model (i.e. a class
584         deriving from osv or osv_memory). The class might be the class passed
585         in argument or, if it inherits from another class, a class constructed
586         by combining the two classes.
587
588         The ``attributes`` argument specifies which parent class attributes
589         have to be combined.
590
591         TODO: the creation of the combined class is repeated at each call of
592         this method. This is probably unnecessary.
593
594         """
595         parent_names = getattr(cls, '_inherit', None)
596         if parent_names:
597             if isinstance(parent_names, (str, unicode)):
598                 name = cls._name or parent_names
599                 parent_names = [parent_names]
600             else:
601                 name = cls._name
602
603             if not name:
604                 raise TypeError('_name is mandatory in case of multiple inheritance')
605
606             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
607                 parent_class = pool.get(parent_name).__class__
608                 if not pool.get(parent_name):
609                     raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
610                         'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
611                 nattr = {}
612                 for s in attributes:
613                     new = copy.copy(getattr(pool.get(parent_name), s))
614                     if s == '_columns':
615                         # Don't _inherit custom fields.
616                         for c in new.keys():
617                             if new[c].manual:
618                                 del new[c]
619                     if hasattr(new, 'update'):
620                         new.update(cls.__dict__.get(s, {}))
621                     elif s=='_constraints':
622                         for c in cls.__dict__.get(s, []):
623                             exist = False
624                             for c2 in range(len(new)):
625                                  #For _constraints, we should check field and methods as well
626                                  if new[c2][2]==c[2] and (new[c2][0] == c[0] \
627                                         or getattr(new[c2][0],'__name__', True) == \
628                                             getattr(c[0],'__name__', False)):
629                                     # If new class defines a constraint with
630                                     # same function name, we let it override
631                                     # the old one.
632                                     new[c2] = c
633                                     exist = True
634                                     break
635                             if not exist:
636                                 new.append(c)
637                     else:
638                         new.extend(cls.__dict__.get(s, []))
639                     nattr[s] = new
640                 cls = type(name, (cls, parent_class), nattr)
641         obj = object.__new__(cls)
642         obj.__init__(pool, cr)
643         return obj
644
645     def __new__(cls):
646         """ Register this model.
647
648         This doesn't create an instance but simply register the model
649         as being part of the module where it is defined.
650
651         TODO make it possible to not even have to call the constructor
652         to be registered.
653
654         """
655
656         # Set the module name (e.g. base, sale, accounting, ...) on the class.
657         module = cls.__module__.split('.')[0]
658         if not hasattr(cls, '_module'):
659             cls._module = module
660
661         # Remember which models to instanciate for this module.
662         module_class_list.setdefault(cls._module, []).append(cls)
663
664         # Since we don't return an instance here, the __init__
665         # method won't be called.
666         return None
667
668     def __init__(self, pool, cr):
669         """ Initialize a model and make it part of the given registry."""
670         pool.add(self._name, self)
671         self.pool = pool
672
673         if not self._name and not hasattr(self, '_inherit'):
674             name = type(self).__name__.split('.')[0]
675             msg = "The class %s has to have a _name attribute" % name
676
677             logger = netsvc.Logger()
678             logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
679             raise except_orm('ValueError', msg)
680
681         if not self._description:
682             self._description = self._name
683         if not self._table:
684             self._table = self._name.replace('.', '_')
685
686     def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
687         """Fetch records as objects allowing to use dot notation to browse fields and relations
688
689         :param cr: database cursor
690         :param user: current user id
691         :param select: id or list of ids
692         :param context: context arguments, like lang, time zone
693         :rtype: object or list of objects requested
694
695         """
696         self._list_class = list_class or browse_record_list
697         cache = {}
698         # need to accepts ints and longs because ids coming from a method
699         # launched by button in the interface have a type long...
700         if isinstance(select, (int, long)):
701             return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
702         elif isinstance(select, list):
703             return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context=context)
704         else:
705             return browse_null()
706
707     def __export_row(self, cr, uid, row, fields, context=None):
708         if context is None:
709             context = {}
710
711         def check_type(field_type):
712             if field_type == 'float':
713                 return 0.0
714             elif field_type == 'integer':
715                 return 0
716             elif field_type == 'boolean':
717                 return False
718             return ''
719
720         def selection_field(in_field):
721             col_obj = self.pool.get(in_field.keys()[0])
722             if f[i] in col_obj._columns.keys():
723                 return  col_obj._columns[f[i]]
724             elif f[i] in col_obj._inherits.keys():
725                 selection_field(col_obj._inherits)
726             else:
727                 return False
728
729         lines = []
730         data = map(lambda x: '', range(len(fields)))
731         done = []
732         for fpos in range(len(fields)):
733             f = fields[fpos]
734             if f:
735                 r = row
736                 i = 0
737                 while i < len(f):
738                     if f[i] == '.id':
739                         r = r['id']
740                     elif f[i] == 'id':
741                         model_data = self.pool.get('ir.model.data')
742                         data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
743                         if len(data_ids):
744                             d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
745                             if d['module']:
746                                 r = '%s.%s' % (d['module'], d['name'])
747                             else:
748                                 r = d['name']
749                         else:
750                             break
751                     else:
752                         r = r[f[i]]
753                         # To display external name of selection field when its exported
754                         cols = False
755                         if f[i] in self._columns.keys():
756                             cols = self._columns[f[i]]
757                         elif f[i] in self._inherit_fields.keys():
758                             cols = selection_field(self._inherits)
759                         if cols and cols._type == 'selection':
760                             sel_list = cols.selection
761                             if r and type(sel_list) == type([]):
762                                 r = [x[1] for x in sel_list if r==x[0]]
763                                 r = r and r[0] or False
764                     if not r:
765                         if f[i] in self._columns:
766                             r = check_type(self._columns[f[i]]._type)
767                         elif f[i] in self._inherit_fields:
768                             r = check_type(self._inherit_fields[f[i]][2]._type)
769                         data[fpos] = r or False
770                         break
771                     if isinstance(r, (browse_record_list, list)):
772                         first = True
773                         fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
774                                 or [], fields)
775                         if fields2 in done:
776                             if [x for x in fields2 if x]:
777                                 break
778                         done.append(fields2)
779                         for row2 in r:
780                             lines2 = self.__export_row(cr, uid, row2, fields2,
781                                     context)
782                             if first:
783                                 for fpos2 in range(len(fields)):
784                                     if lines2 and lines2[0][fpos2]:
785                                         data[fpos2] = lines2[0][fpos2]
786                                 if not data[fpos]:
787                                     dt = ''
788                                     for rr in r:
789                                         name_relation = self.pool.get(rr._table_name)._rec_name
790                                         if isinstance(rr[name_relation], browse_record):
791                                             rr = rr[name_relation]
792                                         rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
793                                         rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
794                                         dt += tools.ustr(rr_name or '') + ','
795                                     data[fpos] = dt[:-1]
796                                     break
797                                 lines += lines2[1:]
798                                 first = False
799                             else:
800                                 lines += lines2
801                         break
802                     i += 1
803                 if i == len(f):
804                     if isinstance(r, browse_record):
805                         r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
806                         r = r and r[0] and r[0][1] or ''
807                     data[fpos] = tools.ustr(r or '')
808         return [data] + lines
809
810     def export_data(self, cr, uid, ids, fields_to_export, context=None):
811         """
812         Export fields for selected objects
813
814         :param cr: database cursor
815         :param uid: current user id
816         :param ids: list of ids
817         :param fields_to_export: list of fields
818         :param context: context arguments, like lang, time zone
819         :rtype: dictionary with a *datas* matrix
820
821         This method is used when exporting data via client menu
822
823         """
824         if context is None:
825             context = {}
826         cols = self._columns.copy()
827         for f in self._inherit_fields:
828             cols.update({f: self._inherit_fields[f][2]})
829         def fsplit(x):
830             if x=='.id': return [x]
831             return x.replace(':id','/id').replace('.id','/.id').split('/')
832         fields_to_export = map(fsplit, fields_to_export)
833         fields_export = fields_to_export + []
834         warning = ''
835         warning_fields = []
836         datas = []
837         for row in self.browse(cr, uid, ids, context):
838             datas += self.__export_row(cr, uid, row, fields_to_export, context)
839         return {'datas': datas}
840
841     def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
842         """
843         Import given data in given module
844
845         :param cr: database cursor
846         :param uid: current user id
847         :param fields: list of fields
848         :param data: data to import
849         :param mode: 'init' or 'update' for record creation
850         :param current_module: module name
851         :param noupdate: flag for record creation
852         :param context: context arguments, like lang, time zone,
853         :param filename: optional file to store partial import state for recovery
854         :rtype: tuple
855
856         This method is used when importing data via client menu.
857
858         Example of fields to import for a sale.order::
859
860             .id,                         (=database_id)
861             partner_id,                  (=name_search)
862             order_line/.id,              (=database_id)
863             order_line/name,
864             order_line/product_id/id,    (=xml id)
865             order_line/price_unit,
866             order_line/product_uom_qty,
867             order_line/product_uom/id    (=xml_id)
868         """
869         if not context:
870             context = {}
871         def _replace_field(x):
872             x = re.sub('([a-z0-9A-Z_])\\.id$', '\\1/.id', x)
873             return x.replace(':id','/id').split('/')
874         fields = map(_replace_field, fields)
875         logger = netsvc.Logger()
876         ir_model_data_obj = self.pool.get('ir.model.data')
877
878         # mode: id (XML id) or .id (database id) or False for name_get
879         def _get_id(model_name, id, current_module=False, mode='id'):
880             if mode=='.id':
881                 id = int(id)
882                 obj_model = self.pool.get(model_name)
883                 ids = obj_model.search(cr, uid, [('id', '=', int(id))])
884                 if not len(ids):
885                     raise Exception(_("Database ID doesn't exist: %s : %s") %(model_name, id))
886             elif mode=='id':
887                 if '.' in id:
888                     module, xml_id = id.rsplit('.', 1)
889                 else:
890                     module, xml_id = current_module, id
891                 record_id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
892                 ir_model_data = ir_model_data_obj.read(cr, uid, [record_id], ['res_id'])
893                 if not ir_model_data:
894                     raise ValueError('No references to %s.%s' % (module, xml_id))
895                 id = ir_model_data[0]['res_id']
896             else:
897                 obj_model = self.pool.get(model_name)
898                 ids = obj_model.name_search(cr, uid, id, operator='=', context=context)
899                 if not ids:
900                     raise ValueError('No record found for %s' % (id,))
901                 id = ids[0][0]
902             return id
903
904         # IN:
905         #   datas: a list of records, each record is defined by a list of values
906         #   prefix: a list of prefix fields ['line_ids']
907         #   position: the line to process, skip is False if it's the first line of the current record
908         # OUT:
909         #   (res, position, warning, res_id) with
910         #     res: the record for the next line to process (including it's one2many)
911         #     position: the new position for the next line
912         #     res_id: the ID of the record if it's a modification
913         def process_liness(self, datas, prefix, current_module, model_name, fields_def, position=0, skip=0):
914             line = datas[position]
915             row = {}
916             warning = []
917             data_res_id = False
918             xml_id = False
919             nbrmax = position+1
920
921             done = {}
922             for i in range(len(fields)):
923                 res = False
924                 if i >= len(line):
925                     raise Exception(_('Please check that all your lines have %d columns.'
926                         'Stopped around line %d having %d columns.') % \
927                             (len(fields), position+2, len(line)))
928                 if not line[i]:
929                     continue
930
931                 field = fields[i]
932                 if field[:len(prefix)] <> prefix:
933                     if line[i] and skip:
934                         return False
935                     continue
936
937                 # ID of the record using a XML ID
938                 if field[len(prefix)]=='id':
939                     try:
940                         data_res_id = _get_id(model_name, line[i], current_module, 'id')
941                     except ValueError, e:
942                         pass
943                     xml_id = line[i]
944                     continue
945
946                 # ID of the record using a database ID
947                 elif field[len(prefix)]=='.id':
948                     data_res_id = _get_id(model_name, line[i], current_module, '.id')
949                     continue
950
951                 # recursive call for getting children and returning [(0,0,{})] or [(1,ID,{})]
952                 if fields_def[field[len(prefix)]]['type']=='one2many':
953                     if field[len(prefix)] in done:
954                         continue
955                     done[field[len(prefix)]] = True
956                     relation_obj = self.pool.get(fields_def[field[len(prefix)]]['relation'])
957                     newfd = relation_obj.fields_get( cr, uid, context=context )
958                     pos = position
959                     res = []
960                     first = 0
961                     while pos < len(datas):
962                         res2 = process_liness(self, datas, prefix + [field[len(prefix)]], current_module, relation_obj._name, newfd, pos, first)
963                         if not res2:
964                             break
965                         (newrow, pos, w2, data_res_id2, xml_id2) = res2
966                         nbrmax = max(nbrmax, pos)
967                         warning += w2
968                         first += 1
969                         if (not newrow) or not reduce(lambda x, y: x or y, newrow.values(), 0):
970                             break
971                         res.append( (data_res_id2 and 1 or 0, data_res_id2 or 0, newrow) )
972
973                 elif fields_def[field[len(prefix)]]['type']=='many2one':
974                     relation = fields_def[field[len(prefix)]]['relation']
975                     if len(field) == len(prefix)+1:
976                         mode = False
977                     else:
978                         mode = field[len(prefix)+1]
979                     res = _get_id(relation, line[i], current_module, mode)
980
981                 elif fields_def[field[len(prefix)]]['type']=='many2many':
982                     relation = fields_def[field[len(prefix)]]['relation']
983                     if len(field) == len(prefix)+1:
984                         mode = False
985                     else:
986                         mode = field[len(prefix)+1]
987
988                     # TODO: improve this by using csv.csv_reader
989                     res = []
990                     for db_id in line[i].split(config.get('csv_internal_sep')):
991                         res.append( _get_id(relation, db_id, current_module, mode) )
992                     res = [(6,0,res)]
993
994                 elif fields_def[field[len(prefix)]]['type'] == 'integer':
995                     res = line[i] and int(line[i]) or 0
996                 elif fields_def[field[len(prefix)]]['type'] == 'boolean':
997                     res = line[i].lower() not in ('0', 'false', 'off')
998                 elif fields_def[field[len(prefix)]]['type'] == 'float':
999                     res = line[i] and float(line[i]) or 0.0
1000                 elif fields_def[field[len(prefix)]]['type'] == 'selection':
1001                     for key, val in fields_def[field[len(prefix)]]['selection']:
1002                         if tools.ustr(line[i]) in [tools.ustr(key), tools.ustr(val)]:
1003                             res = key
1004                             break
1005                     if line[i] and not res:
1006                         logger.notifyChannel("import", netsvc.LOG_WARNING,
1007                                 _("key '%s' not found in selection field '%s'") % \
1008                                         (tools.ustr(line[i]), tools.ustr(field[len(prefix)])))
1009                         warning += [_("Key/value '%s' not found in selection field '%s'") % (tools.ustr(line[i]), tools.ustr(field[len(prefix)]))]
1010
1011                 else:
1012                     res = line[i]
1013
1014                 row[field[len(prefix)]] = res or False
1015
1016             result = (row, nbrmax, warning, data_res_id, xml_id)
1017             return result
1018
1019         fields_def = self.fields_get(cr, uid, context=context)
1020
1021         if config.get('import_partial', False) and filename:
1022             data = pickle.load(file(config.get('import_partial')))
1023             original_value = data.get(filename, 0)
1024
1025         position = 0
1026         while position<len(datas):
1027             res = {}
1028
1029             (res, position, warning, res_id, xml_id) = \
1030                     process_liness(self, datas, [], current_module, self._name, fields_def, position=position)
1031             if len(warning):
1032                 cr.rollback()
1033                 return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
1034
1035             try:
1036                 id = ir_model_data_obj._update(cr, uid, self._name,
1037                      current_module, res, mode=mode, xml_id=xml_id,
1038                      noupdate=noupdate, res_id=res_id, context=context)
1039             except Exception, e:
1040                 return (-1, res, 'Line ' + str(position) +' : ' + str(e), '')
1041
1042             if config.get('import_partial', False) and filename and (not (position%100)):
1043                 data = pickle.load(file(config.get('import_partial')))
1044                 data[filename] = position
1045                 pickle.dump(data, file(config.get('import_partial'), 'wb'))
1046                 if context.get('defer_parent_store_computation'):
1047                     self._parent_store_compute(cr)
1048                 cr.commit()
1049
1050         if context.get('defer_parent_store_computation'):
1051             self._parent_store_compute(cr)
1052         return (position, 0, 0, 0)
1053
1054     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1055         """
1056         Read records with given ids with the given fields
1057
1058         :param cr: database cursor
1059         :param user: current user id
1060         :param ids: id or list of the ids of the records to read
1061         :param fields: optional list of field names to return (default: all fields would be returned)
1062         :type fields: list (example ['field_name_1', ...])
1063         :param context: optional context dictionary - it may contains keys for specifying certain options
1064                         like ``context_lang``, ``context_tz`` to alter the results of the call.
1065                         A special ``bin_size`` boolean flag may also be passed in the context to request the
1066                         value of all fields.binary columns to be returned as the size of the binary instead of its
1067                         contents. This can also be selectively overriden by passing a field-specific flag
1068                         in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1069                         Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1070         :return: list of dictionaries((dictionary per record asked)) with requested field values
1071         :rtype: [{‘name_of_the_field’: value, ...}, ...]
1072         :raise AccessError: * if user has no read rights on the requested object
1073                             * if user tries to bypass access rules for read on the requested object
1074
1075         """
1076         raise NotImplementedError(_('The read method is not implemented on this object !'))
1077
1078     def get_invalid_fields(self, cr, uid):
1079         return list(self._invalids)
1080
1081     def _validate(self, cr, uid, ids, context=None):
1082         context = context or {}
1083         lng = context.get('lang', False) or 'en_US'
1084         trans = self.pool.get('ir.translation')
1085         error_msgs = []
1086         for constraint in self._constraints:
1087             fun, msg, fields = constraint
1088             if not fun(self, cr, uid, ids):
1089                 # Check presence of __call__ directly instead of using
1090                 # callable() because it will be deprecated as of Python 3.0
1091                 if hasattr(msg, '__call__'):
1092                     tmp_msg = msg(self, cr, uid, ids, context=context)
1093                     if isinstance(tmp_msg, tuple):
1094                         tmp_msg, params = tmp_msg
1095                         translated_msg = tmp_msg % params
1096                     else:
1097                         translated_msg = tmp_msg
1098                 else:
1099                     translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg
1100                 error_msgs.append(
1101                         _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1102                 )
1103                 self._invalids.update(fields)
1104         if error_msgs:
1105             cr.rollback()
1106             raise except_orm('ValidateError', '\n'.join(error_msgs))
1107         else:
1108             self._invalids.clear()
1109
1110     def default_get(self, cr, uid, fields_list, context=None):
1111         """
1112         Returns default values for the fields in fields_list.
1113
1114         :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1115         :type fields_list: list
1116         :param context: optional context dictionary - it may contains keys for specifying certain options
1117                         like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1118                         It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1119                         or override a default value for a field.
1120                         A special ``bin_size`` boolean flag may also be passed in the context to request the
1121                         value of all fields.binary columns to be returned as the size of the binary instead of its
1122                         contents. This can also be selectively overriden by passing a field-specific flag
1123                         in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1124                         Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1125         :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
1126         """
1127         # trigger view init hook
1128         self.view_init(cr, uid, fields_list, context)
1129
1130         if not context:
1131             context = {}
1132         defaults = {}
1133
1134         # get the default values for the inherited fields
1135         for t in self._inherits.keys():
1136             defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1137                 context))
1138
1139         # get the default values defined in the object
1140         for f in fields_list:
1141             if f in self._defaults:
1142                 if callable(self._defaults[f]):
1143                     defaults[f] = self._defaults[f](self, cr, uid, context)
1144                 else:
1145                     defaults[f] = self._defaults[f]
1146
1147             fld_def = ((f in self._columns) and self._columns[f]) \
1148                     or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1149                     or False
1150
1151             if isinstance(fld_def, fields.property):
1152                 property_obj = self.pool.get('ir.property')
1153                 prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1154                 if prop_value:
1155                     if isinstance(prop_value, (browse_record, browse_null)):
1156                         defaults[f] = prop_value.id
1157                     else:
1158                         defaults[f] = prop_value
1159                 else:
1160                     if f not in defaults:
1161                         defaults[f] = False
1162
1163         # get the default values set by the user and override the default
1164         # values defined in the object
1165         ir_values_obj = self.pool.get('ir.values')
1166         res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1167         for id, field, field_value in res:
1168             if field in fields_list:
1169                 fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
1170                 if fld_def._type in ('many2one', 'one2one'):
1171                     obj = self.pool.get(fld_def._obj)
1172                     if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1173                         continue
1174                 if fld_def._type in ('many2many'):
1175                     obj = self.pool.get(fld_def._obj)
1176                     field_value2 = []
1177                     for i in range(len(field_value)):
1178                         if not obj.search(cr, uid, [('id', '=',
1179                             field_value[i])]):
1180                             continue
1181                         field_value2.append(field_value[i])
1182                     field_value = field_value2
1183                 if fld_def._type in ('one2many'):
1184                     obj = self.pool.get(fld_def._obj)
1185                     field_value2 = []
1186                     for i in range(len(field_value)):
1187                         field_value2.append({})
1188                         for field2 in field_value[i]:
1189                             if field2 in obj._columns.keys() and obj._columns[field2]._type in ('many2one', 'one2one'):
1190                                 obj2 = self.pool.get(obj._columns[field2]._obj)
1191                                 if not obj2.search(cr, uid,
1192                                         [('id', '=', field_value[i][field2])]):
1193                                     continue
1194                             elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type in ('many2one', 'one2one'):
1195                                 obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1196                                 if not obj2.search(cr, uid,
1197                                         [('id', '=', field_value[i][field2])]):
1198                                     continue
1199                             # TODO add test for many2many and one2many
1200                             field_value2[i][field2] = field_value[i][field2]
1201                     field_value = field_value2
1202                 defaults[field] = field_value
1203
1204         # get the default values from the context
1205         for key in context or {}:
1206             if key.startswith('default_') and (key[8:] in fields_list):
1207                 defaults[key[8:]] = context[key]
1208         return defaults
1209
1210
1211     def perm_read(self, cr, user, ids, context=None, details=True):
1212         raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1213
1214     def unlink(self, cr, uid, ids, context=None):
1215         raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1216
1217     def write(self, cr, user, ids, vals, context=None):
1218         raise NotImplementedError(_('The write method is not implemented on this object !'))
1219
1220     def create(self, cr, user, vals, context=None):
1221         raise NotImplementedError(_('The create method is not implemented on this object !'))
1222
1223     def fields_get_keys(self, cr, user, context=None):
1224         res = self._columns.keys()
1225         for parent in self._inherits:
1226             res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1227         return res
1228
1229     # returns the definition of each field in the object
1230     # the optional fields parameter can limit the result to some fields
1231     def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1232         if context is None:
1233             context = {}
1234         res = {}
1235         translation_obj = self.pool.get('ir.translation')
1236         for parent in self._inherits:
1237             res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1238
1239         if self._columns.keys():
1240             for f in self._columns.keys():
1241                 field_col = self._columns[f]
1242                 if allfields and f not in allfields:
1243                     continue
1244                 res[f] = {'type': field_col._type}
1245                 # This additional attributes for M2M and function field is added
1246                 # because we need to display tooltip with this additional information
1247                 # when client is started in debug mode.
1248                 if isinstance(field_col, fields.function):
1249                     res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
1250                     res[f]['store'] = field_col.store
1251                     if isinstance(field_col.store, dict):
1252                         res[f]['store'] = str(field_col.store)
1253                     res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
1254                     res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
1255                     res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
1256                     res[f]['func_obj'] = field_col._obj or False
1257                     res[f]['func_method'] = field_col._method
1258                 if isinstance(field_col, fields.many2many):
1259                     res[f]['related_columns'] = list((field_col._id1, field_col._id2))
1260                     res[f]['third_table'] = field_col._rel
1261                 for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
1262                         'change_default', 'translate', 'help', 'select', 'selectable'):
1263                     if getattr(field_col, arg):
1264                         res[f][arg] = getattr(field_col, arg)
1265                 if not write_access:
1266                     res[f]['readonly'] = True
1267                     res[f]['states'] = {}
1268                 for arg in ('digits', 'invisible', 'filters'):
1269                     if getattr(field_col, arg, None):
1270                         res[f][arg] = getattr(field_col, arg)
1271
1272                 if field_col.string:
1273                     res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1274                     if res_trans:
1275                         res[f]['string'] = res_trans
1276                 if field_col.help:
1277                     help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1278                     if help_trans:
1279                         res[f]['help'] = help_trans
1280
1281                 if hasattr(field_col, 'selection'):
1282                     if isinstance(field_col.selection, (tuple, list)):
1283                         sel = field_col.selection
1284                         # translate each selection option
1285                         sel2 = []
1286                         for (key, val) in sel:
1287                             val2 = None
1288                             if val:
1289                                 val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1290                             sel2.append((key, val2 or val))
1291                         sel = sel2
1292                         res[f]['selection'] = sel
1293                     else:
1294                         # call the 'dynamic selection' function
1295                         res[f]['selection'] = field_col.selection(self, cr, user, context)
1296                 if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
1297                     res[f]['relation'] = field_col._obj
1298                     res[f]['domain'] = field_col._domain
1299                     res[f]['context'] = field_col._context
1300         else:
1301             #TODO : read the fields from the database
1302             pass
1303
1304         if allfields:
1305             # filter out fields which aren't in the fields list
1306             for r in res.keys():
1307                 if r not in allfields:
1308                     del res[r]
1309         return res
1310
1311     #
1312     # Overload this method if you need a window title which depends on the context
1313     #
1314     def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1315         return False
1316
1317     def __view_look_dom(self, cr, user, node, view_id, context=None):
1318         if not context:
1319             context = {}
1320         result = False
1321         fields = {}
1322         children = True
1323
1324         def encode(s):
1325             if isinstance(s, unicode):
1326                 return s.encode('utf8')
1327             return s
1328
1329         # return True if node can be displayed to current user
1330         def check_group(node):
1331             if node.get('groups'):
1332                 groups = node.get('groups').split(',')
1333                 access_pool = self.pool.get('ir.model.access')
1334                 can_see = any(access_pool.check_groups(cr, user, group) for group in groups)
1335                 if not can_see:
1336                     node.set('invisible', '1')
1337                     if 'attrs' in node.attrib:
1338                         del(node.attrib['attrs']) #avoid making field visible later
1339                 del(node.attrib['groups'])
1340                 return can_see
1341             else:
1342                 return True
1343
1344         if node.tag in ('field', 'node', 'arrow'):
1345             if node.get('object'):
1346                 attrs = {}
1347                 views = {}
1348                 xml = "<form>"
1349                 for f in node:
1350                     if f.tag in ('field'):
1351                         xml += etree.tostring(f, encoding="utf-8")
1352                 xml += "</form>"
1353                 new_xml = etree.fromstring(encode(xml))
1354                 ctx = context.copy()
1355                 ctx['base_model_name'] = self._name
1356                 xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1357                 views['form'] = {
1358                     'arch': xarch,
1359                     'fields': xfields
1360                 }
1361                 attrs = {'views': views}
1362                 fields = xfields
1363             if node.get('name'):
1364                 attrs = {}
1365                 try:
1366                     if node.get('name') in self._columns:
1367                         column = self._columns[node.get('name')]
1368                     else:
1369                         column = self._inherit_fields[node.get('name')][2]
1370                 except Exception:
1371                     column = False
1372
1373                 if column:
1374                     relation = self.pool.get(column._obj)
1375
1376                     children = False
1377                     views = {}
1378                     for f in node:
1379                         if f.tag in ('form', 'tree', 'graph'):
1380                             node.remove(f)
1381                             ctx = context.copy()
1382                             ctx['base_model_name'] = self._name
1383                             xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1384                             views[str(f.tag)] = {
1385                                 'arch': xarch,
1386                                 'fields': xfields
1387                             }
1388                     attrs = {'views': views}
1389                     if node.get('widget') and node.get('widget') == 'selection':
1390                         # Prepare the cached selection list for the client. This needs to be
1391                         # done even when the field is invisible to the current user, because
1392                         # other events could need to change its value to any of the selectable ones
1393                         # (such as on_change events, refreshes, etc.)
1394
1395                         # If domain and context are strings, we keep them for client-side, otherwise
1396                         # we evaluate them server-side to consider them when generating the list of
1397                         # possible values
1398                         # TODO: find a way to remove this hack, by allow dynamic domains
1399                         dom = []
1400                         if column._domain and not isinstance(column._domain, basestring):
1401                             dom = column._domain
1402                         dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
1403                         search_context = dict(context)
1404                         if column._context and not isinstance(column._context, basestring):
1405                             search_context.update(column._context)
1406                         attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
1407                         if (node.get('required') and not int(node.get('required'))) or not column.required:
1408                             attrs['selection'].append((False, ''))
1409                 fields[node.get('name')] = attrs
1410
1411         elif node.tag in ('form', 'tree'):
1412             result = self.view_header_get(cr, user, False, node.tag, context)
1413             if result:
1414                 node.set('string', result)
1415
1416         elif node.tag == 'calendar':
1417             for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1418                 if node.get(additional_field):
1419                     fields[node.get(additional_field)] = {}
1420
1421         if 'groups' in node.attrib:
1422             check_group(node)
1423
1424         # translate view
1425         if ('lang' in context) and not result:
1426             if node.get('string'):
1427                 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
1428                 if trans == node.get('string') and ('base_model_name' in context):
1429                     # If translation is same as source, perhaps we'd have more luck with the alternative model name
1430                     # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
1431                     trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1432                 if trans:
1433                     node.set('string', trans)
1434             if node.get('confirm'):
1435                 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('confirm'))
1436                 if trans:
1437                     node.set('confirm', trans)
1438             if node.get('sum'):
1439                 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('sum'))
1440                 if trans:
1441                     node.set('sum', trans)
1442             if node.get('help'):
1443                 trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('help'))
1444                 if trans:
1445                     node.set('help', trans)
1446
1447         for f in node:
1448             if children or (node.tag == 'field' and f.tag in ('filter','separator')):
1449                 fields.update(self.__view_look_dom(cr, user, f, view_id, context))
1450
1451         return fields
1452
1453     def _disable_workflow_buttons(self, cr, user, node):
1454         if user == 1:
1455             # admin user can always activate workflow buttons
1456             return node
1457
1458         # TODO handle the case of more than one workflow for a model or multiple
1459         # transitions with different groups and same signal
1460         usersobj = self.pool.get('res.users')
1461         buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1462         for button in buttons:
1463             user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1464             cr.execute("""SELECT DISTINCT t.group_id
1465                         FROM wkf
1466                   INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1467                   INNER JOIN wkf_transition t ON (t.act_to = a.id)
1468                        WHERE wkf.osv = %s
1469                          AND t.signal = %s
1470                          AND t.group_id is NOT NULL
1471                    """, (self._name, button.get('name')))
1472             group_ids = [x[0] for x in cr.fetchall() if x[0]]
1473             can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1474             button.set('readonly', str(int(not can_click)))
1475         return node
1476
1477     def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
1478         fields_def = self.__view_look_dom(cr, user, node, view_id, context=context)
1479         node = self._disable_workflow_buttons(cr, user, node)
1480         arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1481         fields = {}
1482         if node.tag == 'diagram':
1483             if node.getchildren()[0].tag == 'node':
1484                 node_fields = self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
1485             if node.getchildren()[1].tag == 'arrow':
1486                 arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
1487             for key, value in node_fields.items():
1488                 fields[key] = value
1489             for key, value in arrow_fields.items():
1490                 fields[key] = value
1491         else:
1492             fields = self.fields_get(cr, user, fields_def.keys(), context)
1493         for field in fields_def:
1494             if field == 'id':
1495                 # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1496                 fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1497             elif field in fields:
1498                 fields[field].update(fields_def[field])
1499             else:
1500                 cr.execute('select name, model from ir_ui_view where (id=%s or inherit_id=%s) and arch like %s', (view_id, view_id, '%%%s%%' % field))
1501                 res = cr.fetchall()[:]
1502                 model = res[0][1]
1503                 res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1504                 msg = "\n * ".join([r[0] for r in res])
1505                 msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
1506                 netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
1507                 raise except_orm('View error', msg)
1508         return arch, fields
1509
1510     def __get_default_calendar_view(self):
1511         """Generate a default calendar view (For internal use only).
1512         """
1513
1514         arch = ('<?xml version="1.0" encoding="utf-8"?>\n'
1515                 '<calendar string="%s"') % (self._description)
1516
1517         if (self._date_name not in self._columns):
1518             date_found = False
1519             for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
1520                 if dt in self._columns:
1521                     self._date_name = dt
1522                     date_found = True
1523                     break
1524
1525             if not date_found:
1526                 raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
1527
1528         if self._date_name:
1529             arch += ' date_start="%s"' % (self._date_name)
1530
1531         for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]:
1532             if color in self._columns:
1533                 arch += ' color="' + color + '"'
1534                 break
1535
1536         dt_stop_flag = False
1537
1538         for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]:
1539             if dt_stop in self._columns:
1540                 arch += ' date_stop="' + dt_stop + '"'
1541                 dt_stop_flag = True
1542                 break
1543
1544         if not dt_stop_flag:
1545             for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]:
1546                 if dt_delay in self._columns:
1547                     arch += ' date_delay="' + dt_delay + '"'
1548                     break
1549
1550         arch += ('>\n'
1551                  '  <field name="%s"/>\n'
1552                  '</calendar>') % (self._rec_name)
1553
1554         return arch
1555
1556     def __get_default_search_view(self, cr, uid, context=None):
1557         form_view = self.fields_view_get(cr, uid, False, 'form', context=context)
1558         tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context)
1559
1560         fields_to_search = set()
1561         fields = self.fields_get(cr, uid, context=context)
1562         for field in fields:
1563             if fields[field].get('select'):
1564                 fields_to_search.add(field)
1565         for view in (form_view, tree_view):
1566             view_root = etree.fromstring(view['arch'])
1567             # Only care about select=1 in xpath below, because select=2 is covered
1568             # by the custom advanced search in clients
1569             fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name"))
1570
1571         tree_view_root = view_root # as provided by loop above
1572         search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")})
1573         field_group = etree.Element("group")
1574         search_view.append(field_group)
1575
1576         for field_name in fields_to_search:
1577             field_group.append(etree.Element("field", attrib={'name': field_name}))
1578
1579         return etree.tostring(search_view, encoding="utf-8").replace('\t', '')
1580
1581     #
1582     # if view_id, view_type is not required
1583     #
1584     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1585         """
1586         Get the detailed composition of the requested view like fields, model, view architecture
1587
1588         :param cr: database cursor
1589         :param user: current user id
1590         :param view_id: id of the view or None
1591         :param view_type: type of the view to return if view_id is None ('form', tree', ...)
1592         :param context: context arguments, like lang, time zone
1593         :param toolbar: true to include contextual actions
1594         :param submenu: example (portal_project module)
1595         :return: dictionary describing the composition of the requested view (including inherited views and extensions)
1596         :raise AttributeError:
1597                             * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
1598                             * if some tag other than 'position' is found in parent view
1599         :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
1600
1601         """
1602         if not context:
1603             context = {}
1604
1605         def encode(s):
1606             if isinstance(s, unicode):
1607                 return s.encode('utf8')
1608             return s
1609
1610         def raise_view_error(error_msg, child_view_id):
1611             view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
1612             raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
1613                                  %  { 'xml_id': child_view.xml_id,
1614                                       'parent_xml_id': view.xml_id,
1615                                       'model': self._name, })
1616
1617         def _inherit_apply(src, inherit, inherit_id=None):
1618             def _find(node, node2):
1619                 if node2.tag == 'xpath':
1620                     res = node.xpath(node2.get('expr'))
1621                     if res:
1622                         return res[0]
1623                     else:
1624                         return None
1625                 else:
1626                     for n in node.getiterator(node2.tag):
1627                         res = True
1628                         if node2.tag == 'field':
1629                             # only compare field names, a field can be only once in a given view
1630                             # at a given level (and for multilevel expressions, we should use xpath
1631                             # inheritance spec anyway)
1632                             if node2.get('name') == n.get('name'):
1633                                 return n
1634                             else:
1635                                 continue
1636                         for attr in node2.attrib:
1637                             if attr == 'position':
1638                                 continue
1639                             if n.get(attr):
1640                                 if n.get(attr) == node2.get(attr):
1641                                     continue
1642                             res = False
1643                         if res:
1644                             return n
1645                 return None
1646
1647             # End: _find(node, node2)
1648
1649             doc_dest = etree.fromstring(encode(inherit))
1650             toparse = [doc_dest]
1651
1652             while len(toparse):
1653                 node2 = toparse.pop(0)
1654                 if isinstance(node2, SKIPPED_ELEMENT_TYPES):
1655                     continue
1656                 if node2.tag == 'data':
1657                     toparse += [ c for c in doc_dest ]
1658                     continue
1659                 node = _find(src, node2)
1660                 if node is not None:
1661                     pos = 'inside'
1662                     if node2.get('position'):
1663                         pos = node2.get('position')
1664                     if pos == 'replace':
1665                         parent = node.getparent()
1666                         if parent is None:
1667                             src = copy.deepcopy(node2[0])
1668                         else:
1669                             for child in node2:
1670                                 node.addprevious(child)
1671                             node.getparent().remove(node)
1672                     elif pos == 'attributes':
1673                         for child in node2.getiterator('attribute'):
1674                             attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
1675                             if attribute[1]:
1676                                 node.set(attribute[0], attribute[1])
1677                             else:
1678                                 del(node.attrib[attribute[0]])
1679                     else:
1680                         sib = node.getnext()
1681                         for child in node2:
1682                             if pos == 'inside':
1683                                 node.append(child)
1684                             elif pos == 'after':
1685                                 if sib is None:
1686                                     node.addnext(child)
1687                                     node = child
1688                                 else:
1689                                     sib.addprevious(child)
1690                             elif pos == 'before':
1691                                 node.addprevious(child)
1692                             else:
1693                                 raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
1694                 else:
1695                     attrs = ''.join([
1696                         ' %s="%s"' % (attr, node2.get(attr))
1697                         for attr in node2.attrib
1698                         if attr != 'position'
1699                     ])
1700                     tag = "<%s%s>" % (node2.tag, attrs)
1701                     raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
1702             return src
1703         # End: _inherit_apply(src, inherit)
1704
1705         result = {'type': view_type, 'model': self._name}
1706
1707         ok = True
1708         model = True
1709         sql_res = False
1710         parent_view_model = None
1711         while ok:
1712             view_ref = context.get(view_type + '_view_ref', False)
1713             if view_ref and not view_id:
1714                 if '.' in view_ref:
1715                     module, view_ref = view_ref.split('.', 1)
1716                     cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
1717                     view_ref_res = cr.fetchone()
1718                     if view_ref_res:
1719                         view_id = view_ref_res[0]
1720
1721             if view_id:
1722                 cr.execute("""SELECT arch,name,field_parent,id,type,inherit_id,model
1723                               FROM ir_ui_view
1724                               WHERE id=%s""", (view_id,))
1725             else:
1726                 cr.execute('''SELECT
1727                         arch,name,field_parent,id,type,inherit_id,model
1728                     FROM
1729                         ir_ui_view
1730                     WHERE
1731                         model=%s AND
1732                         type=%s AND
1733                         inherit_id IS NULL
1734                     ORDER BY priority''', (self._name, view_type))
1735             sql_res = cr.fetchone()
1736
1737             if not sql_res:
1738                 break
1739
1740             ok = sql_res[5]
1741             view_id = ok or sql_res[3]
1742             model = False
1743             parent_view_model = sql_res[6]
1744
1745         # if a view was found
1746         if sql_res:
1747             result['type'] = sql_res[4]
1748             result['view_id'] = sql_res[3]
1749             result['arch'] = sql_res[0]
1750
1751             def _inherit_apply_rec(result, inherit_id):
1752                 # get all views which inherit from (ie modify) this view
1753                 cr.execute('select arch,id from ir_ui_view where inherit_id=%s and model=%s order by priority', (inherit_id, self._name))
1754                 sql_inherit = cr.fetchall()
1755                 for (inherit, id) in sql_inherit:
1756                     result = _inherit_apply(result, inherit, id)
1757                     result = _inherit_apply_rec(result, id)
1758                 return result
1759
1760             inherit_result = etree.fromstring(encode(result['arch']))
1761             result['arch'] = _inherit_apply_rec(inherit_result, sql_res[3])
1762
1763             result['name'] = sql_res[1]
1764             result['field_parent'] = sql_res[2] or False
1765         else:
1766
1767             # otherwise, build some kind of default view
1768             if view_type == 'form':
1769                 res = self.fields_get(cr, user, context=context)
1770                 xml = '<?xml version="1.0" encoding="utf-8"?> ' \
1771                      '<form string="%s">' % (self._description,)
1772                 for x in res:
1773                     if res[x]['type'] not in ('one2many', 'many2many'):
1774                         xml += '<field name="%s"/>' % (x,)
1775                         if res[x]['type'] == 'text':
1776                             xml += "<newline/>"
1777                 xml += "</form>"
1778
1779             elif view_type == 'tree':
1780                 _rec_name = self._rec_name
1781                 if _rec_name not in self._columns:
1782                     _rec_name = self._columns.keys()[0]
1783                 xml = '<?xml version="1.0" encoding="utf-8"?>' \
1784                        '<tree string="%s"><field name="%s"/></tree>' \
1785                        % (self._description, _rec_name)
1786
1787             elif view_type == 'calendar':
1788                 xml = self.__get_default_calendar_view()
1789
1790             elif view_type == 'search':
1791                 xml = self.__get_default_search_view(cr, user, context)
1792
1793             else:
1794                 xml = '<?xml version="1.0"?>' # what happens here, graph case?
1795                 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
1796             result['arch'] = etree.fromstring(encode(xml))
1797             result['name'] = 'default'
1798             result['field_parent'] = False
1799             result['view_id'] = 0
1800
1801         if parent_view_model != self._name:
1802             ctx = context.copy()
1803             ctx['base_model_name'] = parent_view_model
1804         else:
1805             ctx = context
1806         xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
1807         result['arch'] = xarch
1808         result['fields'] = xfields
1809
1810         if submenu:
1811             if context and context.get('active_id', False):
1812                 data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
1813                 if data_menu:
1814                     act_id = data_menu.id
1815                     if act_id:
1816                         data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
1817                         result['submenu'] = getattr(data_action, 'menus', False)
1818         if toolbar:
1819             def clean(x):
1820                 x = x[2]
1821                 for key in ('report_sxw_content', 'report_rml_content',
1822                         'report_sxw', 'report_rml',
1823                         'report_sxw_content_data', 'report_rml_content_data'):
1824                     if key in x:
1825                         del x[key]
1826                 return x
1827             ir_values_obj = self.pool.get('ir.values')
1828             resprint = ir_values_obj.get(cr, user, 'action',
1829                     'client_print_multi', [(self._name, False)], False,
1830                     context)
1831             resaction = ir_values_obj.get(cr, user, 'action',
1832                     'client_action_multi', [(self._name, False)], False,
1833                     context)
1834
1835             resrelate = ir_values_obj.get(cr, user, 'action',
1836                     'client_action_relate', [(self._name, False)], False,
1837                     context)
1838             resprint = map(clean, resprint)
1839             resaction = map(clean, resaction)
1840             resaction = filter(lambda x: not x.get('multi', False), resaction)
1841             resprint = filter(lambda x: not x.get('multi', False), resprint)
1842             resrelate = map(lambda x: x[2], resrelate)
1843
1844             for x in resprint + resaction + resrelate:
1845                 x['string'] = x['name']
1846
1847             result['toolbar'] = {
1848                 'print': resprint,
1849                 'action': resaction,
1850                 'relate': resrelate
1851             }
1852         return result
1853
1854     _view_look_dom_arch = __view_look_dom_arch
1855
1856     def search_count(self, cr, user, args, context=None):
1857         if not context:
1858             context = {}
1859         res = self.search(cr, user, args, context=context, count=True)
1860         if isinstance(res, list):
1861             return len(res)
1862         return res
1863
1864     def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
1865         """
1866         Search for records based on a search domain.
1867
1868         :param cr: database cursor
1869         :param user: current user id
1870         :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
1871         :param offset: optional number of results to skip in the returned values (default: 0)
1872         :param limit: optional max number of records to return (default: **None**)
1873         :param order: optional columns to sort by (default: self._order=id )
1874         :param context: optional context arguments, like lang, time zone
1875         :type context: dictionary
1876         :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
1877         :return: id or list of ids of records matching the criteria
1878         :rtype: integer or list of integers
1879         :raise AccessError: * if user tries to bypass access rules for read on the requested object.
1880
1881         **Expressing a search domain (args)**
1882
1883         Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
1884
1885             * **field_name** must be a valid name of field of the object model, possibly following many-to-one relationships using dot-notation, e.g 'street' or 'partner_id.country' are valid values.
1886             * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
1887               The semantics of most of these operators are obvious.
1888               The ``child_of`` operator will look for records who are children or grand-children of a given record,
1889               according to the semantics of this model (i.e following the relationship field named by
1890               ``self._parent_name``, by default ``parent_id``.
1891             * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
1892
1893         Domain criteria can be combined using 3 logical operators than can be added between tuples:  '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
1894         These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
1895         Be very careful about this when you combine them the first time.
1896
1897         Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
1898
1899             [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
1900
1901         The '&' is omitted as it is the default, and of course we could have used '!=' for the language, but what this domain really represents is::
1902
1903             (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
1904
1905         """
1906         return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
1907
1908     def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
1909         """
1910         Private implementation of search() method, allowing specifying the uid to use for the access right check.
1911         This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
1912         by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
1913
1914         :param access_rights_uid: optional user ID to use when checking access rights
1915                                   (not for ir.rules, this is only for ir.model.access)
1916         """
1917         raise NotImplementedError(_('The search method is not implemented on this object !'))
1918
1919     def name_get(self, cr, user, ids, context=None):
1920         """
1921
1922         :param cr: database cursor
1923         :param user: current user id
1924         :type user: integer
1925         :param ids: list of ids
1926         :param context: context arguments, like lang, time zone
1927         :type context: dictionary
1928         :return: tuples with the text representation of requested objects for to-many relationships
1929
1930         """
1931         if not context:
1932             context = {}
1933         if not ids:
1934             return []
1935         if isinstance(ids, (int, long)):
1936             ids = [ids]
1937         return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids,
1938             [self._rec_name], context, load='_classic_write')]
1939
1940     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
1941         """
1942         Search for records and their display names according to a search domain.
1943
1944         :param cr: database cursor
1945         :param user: current user id
1946         :param name: object name to search
1947         :param args: list of tuples specifying search criteria [('field_name', 'operator', 'value'), ...]
1948         :param operator: operator for search criterion
1949         :param context: context arguments, like lang, time zone
1950         :type context: dictionary
1951         :param limit: optional max number of records to return
1952         :return: list of object names matching the search criteria, used to provide completion for to-many relationships
1953
1954         This method is equivalent of :py:meth:`~osv.osv.osv.search` on **name** + :py:meth:`~osv.osv.osv.name_get` on the result.
1955         See :py:meth:`~osv.osv.osv.search` for an explanation of the possible values for the search domain specified in **args**.
1956
1957         """
1958         return self._name_search(cr, user, name, args, operator, context, limit)
1959
1960     # private implementation of name_search, allows passing a dedicated user for the name_get part to
1961     # solve some access rights issues
1962     def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1963         if args is None:
1964             args = []
1965         if context is None:
1966             context = {}
1967         args = args[:]
1968         if name:
1969             args += [(self._rec_name, operator, name)]
1970         access_rights_uid = name_get_uid or user
1971         ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
1972         res = self.name_get(cr, access_rights_uid, ids, context)
1973         return res
1974
1975     def copy(self, cr, uid, id, default=None, context=None):
1976         raise NotImplementedError(_('The copy method is not implemented on this object !'))
1977
1978     def exists(self, cr, uid, id, context=None):
1979         raise NotImplementedError(_('The exists method is not implemented on this object !'))
1980
1981     def read_string(self, cr, uid, id, langs, fields=None, context=None):
1982         res = {}
1983         res2 = {}
1984         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read', context=context)
1985         if not fields:
1986             fields = self._columns.keys() + self._inherit_fields.keys()
1987         #FIXME: collect all calls to _get_source into one SQL call.
1988         for lang in langs:
1989             res[lang] = {'code': lang}
1990             for f in fields:
1991                 if f in self._columns:
1992                     res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
1993                     if res_trans:
1994                         res[lang][f] = res_trans
1995                     else:
1996                         res[lang][f] = self._columns[f].string
1997         for table in self._inherits:
1998             cols = intersect(self._inherit_fields.keys(), fields)
1999             res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
2000         for lang in res2:
2001             if lang in res:
2002                 res[lang]['code'] = lang
2003             for f in res2[lang]:
2004                 res[lang][f] = res2[lang][f]
2005         return res
2006
2007     def write_string(self, cr, uid, id, langs, vals, context=None):
2008         self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write', context=context)
2009         #FIXME: try to only call the translation in one SQL
2010         for lang in langs:
2011             for field in vals:
2012                 if field in self._columns:
2013                     src = self._columns[field].string
2014                     self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
2015         for table in self._inherits:
2016             cols = intersect(self._inherit_fields.keys(), vals)
2017             if cols:
2018                 self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2019         return True
2020
2021     def _check_removed_columns(self, cr, log=False):
2022         raise NotImplementedError()
2023
2024     def _add_missing_default_values(self, cr, uid, values, context=None):
2025         missing_defaults = []
2026         avoid_tables = [] # avoid overriding inherited values when parent is set
2027         for tables, parent_field in self._inherits.items():
2028             if parent_field in values:
2029                 avoid_tables.append(tables)
2030         for field in self._columns.keys():
2031             if not field in values:
2032                 missing_defaults.append(field)
2033         for field in self._inherit_fields.keys():
2034             if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
2035                 missing_defaults.append(field)
2036
2037         if len(missing_defaults):
2038             # override defaults with the provided values, never allow the other way around
2039             defaults = self.default_get(cr, uid, missing_defaults, context)
2040             for dv in defaults:
2041                 if ((dv in self._columns and self._columns[dv]._type == 'many2many') \
2042                      or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many')) \
2043                         and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
2044                     defaults[dv] = [(6, 0, defaults[dv])]
2045                 if (dv in self._columns and self._columns[dv]._type == 'one2many' \
2046                     or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many')) \
2047                         and isinstance(defaults[dv], (list, tuple)) and defaults[dv] and isinstance(defaults[dv][0], dict):
2048                     defaults[dv] = [(0, 0, x) for x in defaults[dv]]
2049             defaults.update(values)
2050             values = defaults
2051         return values
2052
2053 class orm_memory(orm_template):
2054
2055     _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
2056     _inherit_fields = {}
2057     _max_count = None
2058     _max_hours = None
2059     _check_time = 20
2060
2061     @classmethod
2062     def createInstance(cls, pool, cr):
2063         return cls.makeInstance(pool, cr, ['_columns', '_defaults'])
2064
2065     def __init__(self, pool, cr):
2066         super(orm_memory, self).__init__(pool, cr)
2067         self.datas = {}
2068         self.next_id = 0
2069         self.check_id = 0
2070         self._max_count = config.get('osv_memory_count_limit')
2071         self._max_hours = config.get('osv_memory_age_limit')
2072         cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2073
2074     def _check_access(self, uid, object_id, mode):
2075         if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2076             raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2077
2078     def vaccum(self, cr, uid, force=False):
2079         """Run the vaccuum cleaning system, expiring and removing old records from the
2080         virtual osv_memory tables if the "max count" or "max age" conditions are enabled
2081         and have been reached. This method can be called very often (e.g. everytime a record
2082         is created), but will only actually trigger the cleanup process once out of
2083         "_check_time" times (by default once out of 20 calls)."""
2084         self.check_id += 1
2085         if (not force) and (self.check_id % self._check_time):
2086             return True
2087         tounlink = []
2088
2089         # Age-based expiration
2090         if self._max_hours:
2091             max = time.time() - self._max_hours * 60 * 60
2092             for k,v in self.datas.iteritems():
2093                 if v['internal.date_access'] < max:
2094                     tounlink.append(k)
2095             self.unlink(cr, 1, tounlink)
2096
2097         # Count-based expiration
2098         if self._max_count and len(self.datas) > self._max_count:
2099             # sort by access time to remove only the first/oldest ones in LRU fashion
2100             records = self.datas.items()
2101             records.sort(key=lambda x:x[1]['internal.date_access'])
2102             self.unlink(cr, 1, [x[0] for x in records[:len(self.datas)-self._max_count]])
2103
2104         return True
2105
2106     def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2107         if not context:
2108             context = {}
2109         if not fields_to_read:
2110             fields_to_read = self._columns.keys()
2111         result = []
2112         if self.datas:
2113             ids_orig = ids
2114             if isinstance(ids, (int, long)):
2115                 ids = [ids]
2116             for id in ids:
2117                 r = {'id': id}
2118                 for f in fields_to_read:
2119                     record = self.datas.get(id)
2120                     if record:
2121                         self._check_access(user, id, 'read')
2122                         r[f] = record.get(f, False)
2123                         if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2124                             r[f] = len(r[f])
2125                 result.append(r)
2126                 if id in self.datas:
2127                     self.datas[id]['internal.date_access'] = time.time()
2128             fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2129             for f in fields_post:
2130                 res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2131                 for record in result:
2132                     record[f] = res2[record['id']]
2133             if isinstance(ids_orig, (int, long)):
2134                 return result[0]
2135         return result
2136
2137     def write(self, cr, user, ids, vals, context=None):
2138         if not ids:
2139             return True
2140         vals2 = {}
2141         upd_todo = []
2142         for field in vals:
2143             if self._columns[field]._classic_write:
2144                 vals2[field] = vals[field]
2145             else:
2146                 upd_todo.append(field)
2147         for object_id in ids:
2148             self._check_access(user, object_id, mode='write')
2149             self.datas[object_id].update(vals2)
2150             self.datas[object_id]['internal.date_access'] = time.time()
2151             for field in upd_todo:
2152                 self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2153         self._validate(cr, user, [object_id], context)
2154         wf_service = netsvc.LocalService("workflow")
2155         wf_service.trg_write(user, self._name, object_id, cr)
2156         return object_id
2157
2158     def create(self, cr, user, vals, context=None):
2159         self.vaccum(cr, user)
2160         self.next_id += 1
2161         id_new = self.next_id
2162
2163         vals = self._add_missing_default_values(cr, user, vals, context)
2164
2165         vals2 = {}
2166         upd_todo = []
2167         for field in vals:
2168             if self._columns[field]._classic_write:
2169                 vals2[field] = vals[field]
2170             else:
2171                 upd_todo.append(field)
2172         self.datas[id_new] = vals2
2173         self.datas[id_new]['internal.date_access'] = time.time()
2174         self.datas[id_new]['internal.create_uid'] = user
2175
2176         for field in upd_todo:
2177             self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2178         self._validate(cr, user, [id_new], context)
2179         if self._log_create and not (context and context.get('no_store_function', False)):
2180             message = self._description + \
2181                 " '" + \
2182                 self.name_get(cr, user, [id_new], context=context)[0][1] + \
2183                 "' "+ _("created.")
2184             self.log(cr, user, id_new, message, True, context=context)
2185         wf_service = netsvc.LocalService("workflow")
2186         wf_service.trg_create(user, self._name, id_new, cr)
2187         return id_new
2188
2189     def _where_calc(self, cr, user, args, active_test=True, context=None):
2190         if not context:
2191             context = {}
2192         args = args[:]
2193         res = []
2194         # if the object has a field named 'active', filter out all inactive
2195         # records unless they were explicitely asked for
2196         if 'active' in self._columns and (active_test and context.get('active_test', True)):
2197             if args:
2198                 active_in_args = False
2199                 for a in args:
2200                     if a[0] == 'active':
2201                         active_in_args = True
2202                 if not active_in_args:
2203                     args.insert(0, ('active', '=', 1))
2204             else:
2205                 args = [('active', '=', 1)]
2206         if args:
2207             import expression
2208             e = expression.expression(args)
2209             e.parse(cr, user, self, context)
2210             res = e.exp
2211         return res or []
2212
2213     def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2214         if not context:
2215             context = {}
2216
2217         # implicit filter on current user except for superuser
2218         if user != 1:
2219             if not args:
2220                 args = []
2221             args.insert(0, ('internal.create_uid', '=', user))
2222
2223         result = self._where_calc(cr, user, args, context=context)
2224         if result == []:
2225             return self.datas.keys()
2226
2227         res = []
2228         counter = 0
2229         #Find the value of dict
2230         f = False
2231         if result:
2232             for id, data in self.datas.items():
2233                 counter = counter + 1
2234                 data['id'] = id
2235                 if limit and (counter > int(limit)):
2236                     break
2237                 f = True
2238                 for arg in result:
2239                     if arg[1] == '=':
2240                         val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2241                     elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2242                         val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2243                     elif arg[1] in ['ilike']:
2244                         val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2245
2246                     f = f and val
2247
2248                 if f:
2249                     res.append(id)
2250         if count:
2251             return len(res)
2252         return res or []
2253
2254     def unlink(self, cr, uid, ids, context=None):
2255         for id in ids:
2256             self._check_access(uid, id, 'unlink')
2257             self.datas.pop(id, None)
2258         if len(ids):
2259             cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2260         return True
2261
2262     def perm_read(self, cr, user, ids, context=None, details=True):
2263         result = []
2264         credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2265         create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2266         for id in ids:
2267             self._check_access(user, id, 'read')
2268             result.append({
2269                 'create_uid': credentials,
2270                 'create_date': create_date,
2271                 'write_uid': False,
2272                 'write_date': False,
2273                 'id': id,
2274                 'xmlid' : False,
2275             })
2276         return result
2277
2278     def _check_removed_columns(self, cr, log=False):
2279         # nothing to check in memory...
2280         pass
2281
2282     def exists(self, cr, uid, id, context=None):
2283         return id in self.datas
2284
2285 class orm(orm_template):
2286     _sql_constraints = []
2287     _table = None
2288     _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
2289     __logger = logging.getLogger('orm')
2290     __schema = logging.getLogger('orm.schema')
2291     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2292         """
2293         Get the list of records in list view grouped by the given ``groupby`` fields
2294
2295         :param cr: database cursor
2296         :param uid: current user id
2297         :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
2298         :param list fields: list of fields present in the list view specified on the object
2299         :param list groupby: fields by which the records will be grouped
2300         :param int offset: optional number of records to skip
2301         :param int limit: optional max number of records to return
2302         :param dict context: context arguments, like lang, time zone
2303         :param order: optional ``order by`` specification, for overriding the natural
2304                       sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
2305                       (supported only for many2one fields currently)
2306         :return: list of dictionaries(one dictionary for each record) containing:
2307
2308                     * the values of fields grouped by the fields in ``groupby`` argument
2309                     * __domain: list of tuples specifying the search criteria
2310                     * __context: dictionary with argument like ``groupby``
2311         :rtype: [{'field_name_1': value, ...]
2312         :raise AccessError: * if user has no read rights on the requested object
2313                             * if user tries to bypass access rules for read on the requested object
2314
2315         """
2316         context = context or {}
2317         self.pool.get('ir.model.access').check(cr, uid, self._name, 'read', context=context)
2318         if not fields:
2319             fields = self._columns.keys()
2320
2321         query = self._where_calc(cr, uid, domain, context=context)
2322         self._apply_ir_rules(cr, uid, query, 'read', context=context)
2323
2324         # Take care of adding join(s) if groupby is an '_inherits'ed field
2325         groupby_list = groupby
2326         qualified_groupby_field = groupby
2327         if groupby:
2328             if isinstance(groupby, list):
2329                 groupby = groupby[0]
2330             qualified_groupby_field = self._inherits_join_calc(groupby, query)
2331
2332         if groupby:
2333             assert not groupby or groupby in fields, "Fields in 'groupby' must appear in the list of fields to read (perhaps it's missing in the list view?)"
2334             groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2335             assert groupby_def and groupby_def._classic_write, "Fields in 'groupby' must be regular database-persisted fields (no function or related fields), or function fields with store=True"
2336
2337         fget = self.fields_get(cr, uid, fields)
2338         float_int_fields = filter(lambda x: fget[x]['type'] in ('float', 'integer'), fields)
2339         flist = ''
2340         group_count = group_by = groupby
2341         if groupby:
2342             if fget.get(groupby):
2343                 if fget[groupby]['type'] in ('date', 'datetime'):
2344                     flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
2345                     groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
2346                     qualified_groupby_field = groupby
2347                 else:
2348                     flist = qualified_groupby_field
2349             else:
2350                 # Don't allow arbitrary values, as this would be a SQL injection vector!
2351                 raise except_orm(_('Invalid group_by'),
2352                                  _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2353
2354
2355         fields_pre = [f for f in float_int_fields if
2356                    f == self.CONCURRENCY_CHECK_FIELD
2357                 or (f in self._columns and getattr(self._columns[f], '_classic_write'))]
2358         for f in fields_pre:
2359             if f not in ['id', 'sequence']:
2360                 group_operator = fget[f].get('group_operator', 'sum')
2361                 if flist:
2362                     flist += ', '
2363                 qualified_field = '"%s"."%s"' % (self._table, f)
2364                 flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2365
2366         gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2367
2368         from_clause, where_clause, where_clause_params = query.get_sql()
2369         where_clause = where_clause and ' WHERE ' + where_clause
2370         limit_str = limit and ' limit %d' % limit or ''
2371         offset_str = offset and ' offset %d' % offset or ''
2372         if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2373             group_count = '_'
2374         cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + limit_str + offset_str, where_clause_params)
2375         alldata = {}
2376         groupby = group_by
2377         for r in cr.dictfetchall():
2378             for fld, val in r.items():
2379                 if val == None: r[fld] = False
2380             alldata[r['id']] = r
2381             del r['id']
2382
2383         data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=orderby or groupby, context=context)
2384         # the IDS of records that have groupby field value = False or '' should be sorted too
2385         data_ids += filter(lambda x:x not in data_ids, alldata.keys())
2386         data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
2387         # restore order of the search as read() uses the default _order (this is only for groups, so the size of data_read shoud be small):
2388         data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
2389
2390         for d in data:
2391             if groupby:
2392                 d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2393                 if not isinstance(groupby_list, (str, unicode)):
2394                     if groupby or not context.get('group_by_no_leaf', False):
2395                         d['__context'] = {'group_by': groupby_list[1:]}
2396             if groupby and groupby in fget:
2397                 if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2398                     dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2399                     days = calendar.monthrange(dt.year, dt.month)[1]
2400
2401                     d[groupby] = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d').strftime('%B %Y')
2402                     d['__domain'] = [(groupby, '>=', alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-01', '%Y-%m-%d').strftime('%Y-%m-%d') or False),\
2403                                      (groupby, '<=', alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-' + str(days), '%Y-%m-%d').strftime('%Y-%m-%d') or False)] + domain
2404                 del alldata[d['id']][groupby]
2405             d.update(alldata[d['id']])
2406             del d['id']
2407         return data
2408
2409     def _inherits_join_add(self, parent_model_name, query):
2410         """
2411         Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
2412
2413         :param parent_model_name: name of the parent model for which the clauses should be added
2414         :param query: query object on which the JOIN should be added
2415         """
2416         inherits_field = self._inherits[parent_model_name]
2417         parent_model = self.pool.get(parent_model_name)
2418         parent_table_name = parent_model._table
2419         quoted_parent_table_name = '"%s"' % parent_table_name
2420         if quoted_parent_table_name not in query.tables:
2421             query.tables.append(quoted_parent_table_name)
2422             query.where_clause.append('("%s".%s = %s.id)' % (self._table, inherits_field, parent_table_name))
2423
2424     def _inherits_join_calc(self, field, query):
2425         """
2426         Adds missing table select and join clause(s) to ``query`` for reaching
2427         the field coming from an '_inherits' parent table (no duplicates).
2428
2429         :param field: name of inherited field to reach
2430         :param query: query object on which the JOIN should be added
2431         :return: qualified name of field, to be used in SELECT clause
2432         """
2433         current_table = self
2434         while field in current_table._inherit_fields and not field in current_table._columns:
2435             parent_model_name = current_table._inherit_fields[field][0]
2436             parent_table = self.pool.get(parent_model_name)
2437             self._inherits_join_add(parent_model_name, query)
2438             current_table = parent_table
2439         return '"%s".%s' % (current_table._table, field)
2440
2441     def _parent_store_compute(self, cr):
2442         if not self._parent_store:
2443             return
2444         logger = netsvc.Logger()
2445         logger.notifyChannel('data', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, ))
2446         def browse_rec(root, pos=0):
2447 # TODO: set order
2448             where = self._parent_name+'='+str(root)
2449             if not root:
2450                 where = self._parent_name+' IS NULL'
2451             if self._parent_order:
2452                 where += ' order by '+self._parent_order
2453             cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2454             pos2 = pos + 1
2455             for id in cr.fetchall():
2456                 pos2 = browse_rec(id[0], pos2)
2457             cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2458             return pos2 + 1
2459         query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2460         if self._parent_order:
2461             query += ' order by ' + self._parent_order
2462         pos = 0
2463         cr.execute(query)
2464         for (root,) in cr.fetchall():
2465             pos = browse_rec(root, pos)
2466         return True
2467
2468     def _update_store(self, cr, f, k):
2469         logger = netsvc.Logger()
2470         logger.notifyChannel('data', netsvc.LOG_INFO, "storing computed values of fields.function '%s'" % (k,))
2471         ss = self._columns[k]._symbol_set
2472         update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2473         cr.execute('select id from '+self._table)
2474         ids_lst = map(lambda x: x[0], cr.fetchall())
2475         while ids_lst:
2476             iids = ids_lst[:40]
2477             ids_lst = ids_lst[40:]
2478             res = f.get(cr, self, iids, k, 1, {})
2479             for key, val in res.items():
2480                 if f._multi:
2481                     val = val[k]
2482                 # if val is a many2one, just write the ID
2483                 if type(val) == tuple:
2484                     val = val[0]
2485                 if (val<>False) or (type(val)<>bool):
2486                     cr.execute(update_query, (ss[1](val), key))
2487
2488     def _check_selection_field_value(self, cr, uid, field, value, context=None):
2489         """Raise except_orm if value is not among the valid values for the selection field"""
2490         if self._columns[field]._type == 'reference':
2491             val_model, val_id_str = value.split(',', 1)
2492             val_id = False
2493             try:
2494                 val_id = long(val_id_str)
2495             except ValueError:
2496                 pass
2497             if not val_id:
2498                 raise except_orm(_('ValidateError'),
2499                                  _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value))
2500             val = val_model
2501         else:
2502             val = value
2503         if isinstance(self._columns[field].selection, (tuple, list)):
2504             if val in dict(self._columns[field].selection):
2505                 return
2506         elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2507             return
2508         raise except_orm(_('ValidateError'),
2509                          _('The value "%s" for the field "%s" is not in the selection') % (value, field))
2510
2511     def _check_removed_columns(self, cr, log=False):
2512         # iterate on the database columns to drop the NOT NULL constraints
2513         # of fields which were required but have been removed (or will be added by another module)
2514         columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
2515         columns += ('id', 'write_uid', 'write_date', 'create_uid', 'create_date') # openerp access columns
2516         cr.execute("SELECT a.attname, a.attnotnull"
2517                    "  FROM pg_class c, pg_attribute a"
2518                    " WHERE c.relname=%s"
2519                    "   AND c.oid=a.attrelid"
2520                    "   AND a.attisdropped=%s"
2521                    "   AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2522                    "   AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2523
2524         for column in cr.dictfetchall():
2525             if log:
2526                 self.__logger.debug("column %s is in the table %s but not in the corresponding object %s",
2527                                     column['attname'], self._table, self._name)
2528             if column['attnotnull']:
2529                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
2530                 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2531                                     self._table, column['attname'])
2532
2533     def _auto_init(self, cr, context=None):
2534         """
2535
2536         Call _field_create and, unless _auto is False:
2537
2538         - create the corresponding table in database for the model,
2539         - possibly add the parent columns in database,
2540         - possibly add the columns 'create_uid', 'create_date', 'write_uid',
2541           'write_date' in database if _log_access is True (the default),
2542         - report on database columns no more existing in _columns,
2543         - remove no more existing not null constraints,
2544         - alter existing database columns to match _columns,
2545         - create database tables to match _columns,
2546         - add database indices to match _columns,
2547
2548         """
2549         raise_on_invalid_object_name(self._name)
2550         if context is None:
2551             context = {}
2552         store_compute = False
2553         create = False
2554         todo_end = []
2555         self._field_create(cr, context=context)
2556         if getattr(self, '_auto', True):
2557             cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2558             if not cr.rowcount:
2559                 cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
2560                 cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
2561                 create = True
2562                 self.__schema.debug("Table '%s': created", self._table)
2563
2564             cr.commit()
2565             if self._parent_store:
2566                 cr.execute("""SELECT c.relname
2567                     FROM pg_class c, pg_attribute a
2568                     WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2569                     """, (self._table, 'parent_left'))
2570                 if not cr.rowcount:
2571                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
2572                     cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
2573                     if 'parent_left' not in self._columns:
2574                         self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
2575                                             self._table)
2576                         self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2577                                             self._table, 'parent_left', 'INTEGER')
2578                     elif not self._columns['parent_left'].select:
2579                         self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
2580                                             self._table)
2581                     if 'parent_right' not in self._columns:
2582                         self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
2583                                             self._table)
2584                         self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2585                                             self._table, 'parent_right', 'INTEGER')
2586                     elif not self._columns['parent_right'].select:
2587                         self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
2588                                             self._table)
2589                     if self._columns[self._parent_name].ondelete != 'cascade':
2590                         self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
2591                                             self._parent_name, self._name)
2592
2593                     cr.commit()
2594                     store_compute = True
2595
2596             if self._log_access:
2597                 logs = {
2598                     'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2599                     'create_date': 'TIMESTAMP',
2600                     'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2601                     'write_date': 'TIMESTAMP'
2602                 }
2603                 for k in logs:
2604                     cr.execute("""
2605                         SELECT c.relname
2606                           FROM pg_class c, pg_attribute a
2607                          WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
2608                         """, (self._table, k))
2609                     if not cr.rowcount:
2610                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
2611                         cr.commit()
2612                         self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2613                                             self._table, k, logs[k])
2614
2615             self._check_removed_columns(cr, log=False)
2616
2617             # iterate on the "object columns"
2618             todo_update_store = []
2619             update_custom_fields = context.get('update_custom_fields', False)
2620
2621             cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
2622                "FROM pg_class c,pg_attribute a,pg_type t " \
2623                "WHERE c.relname=%s " \
2624                "AND c.oid=a.attrelid " \
2625                "AND a.atttypid=t.oid", (self._table,))
2626             col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
2627
2628
2629             for k in self._columns:
2630                 if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
2631                     continue
2632                 #Not Updating Custom fields
2633                 if k.startswith('x_') and not update_custom_fields:
2634                     continue
2635
2636                 f = self._columns[k]
2637
2638                 if isinstance(f, fields.one2many):
2639                     cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
2640
2641                     if self.pool.get(f._obj):
2642                         if f._fields_id not in self.pool.get(f._obj)._columns.keys():
2643                             if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
2644                                 raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
2645
2646                     if cr.fetchone():
2647                         cr.execute("SELECT count(1) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj, f._fields_id))
2648                         res = cr.fetchone()[0]
2649                         if not res:
2650                             cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
2651                             self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
2652                                 self._obj, f._fields_id, f._table)
2653                 elif isinstance(f, fields.many2many):
2654                     cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
2655                     if not cr.dictfetchall():
2656                         if not self.pool.get(f._obj):
2657                             raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2658                         ref = self.pool.get(f._obj)._table
2659 #                        ref = f._obj.replace('.', '_')
2660                         cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, UNIQUE("%s","%s")) WITH OIDS' % (f._rel, f._id1, self._table, f._id2, ref, f._id1, f._id2))
2661                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
2662                         cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
2663                         cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
2664                         cr.commit()
2665                         self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
2666                                             f._rel, self._table, ref)
2667                 else:
2668                     res = col_data.get(k, [])
2669                     res = res and [res] or []
2670                     if not res and hasattr(f, 'oldname'):
2671                         cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
2672                             "FROM pg_class c,pg_attribute a,pg_type t " \
2673                             "WHERE c.relname=%s " \
2674                             "AND a.attname=%s " \
2675                             "AND c.oid=a.attrelid " \
2676                             "AND a.atttypid=t.oid", (self._table, f.oldname))
2677                         res_old = cr.dictfetchall()
2678                         if res_old and len(res_old) == 1:
2679                             cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
2680                             res = res_old
2681                             res[0]['attname'] = k
2682                             self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
2683                                                 self._table, f.oldname, k)
2684
2685                     if len(res) == 1:
2686                         f_pg_def = res[0]
2687                         f_pg_type = f_pg_def['typname']
2688                         f_pg_size = f_pg_def['size']
2689                         f_pg_notnull = f_pg_def['attnotnull']
2690                         if isinstance(f, fields.function) and not f.store and\
2691                                 not getattr(f, 'nodrop', False):
2692                             self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
2693                                                k, f.string, self._table)
2694                             cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
2695                             cr.commit()
2696                             self.__schema.debug("Table '%s': dropped column '%s' with cascade",
2697                                                  self._table, k)
2698                             f_obj_type = None
2699                         else:
2700                             f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
2701
2702                         if f_obj_type:
2703                             ok = False
2704                             casts = [
2705                                 ('text', 'char', 'VARCHAR(%d)' % (f.size or 0,), '::VARCHAR(%d)'%(f.size or 0,)),
2706                                 ('varchar', 'text', 'TEXT', ''),
2707                                 ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2708                                 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2709                                 ('timestamp', 'date', 'date', '::date'),
2710                                 ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2711                                 ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
2712                             ]
2713                             if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
2714                                 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2715                                 cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" VARCHAR(%d)' % (self._table, k, f.size))
2716                                 cr.execute('UPDATE "%s" SET "%s"=temp_change_size::VARCHAR(%d)' % (self._table, k, f.size))
2717                                 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2718                                 cr.commit()
2719                                 self.__schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
2720                                     self._table, k, f_pg_size, f.size)
2721                             for c in casts:
2722                                 if (f_pg_type==c[0]) and (f._type==c[1]):
2723                                     if f_pg_type != f_obj_type:
2724                                         ok = True
2725                                         cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
2726                                         cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
2727                                         cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
2728                                         cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
2729                                         cr.commit()
2730                                         self.__schema.debug("Table '%s': column '%s' changed type from %s to %s",
2731                                             self._table, k, c[0], c[1])
2732                                     break
2733
2734                             if f_pg_type != f_obj_type:
2735                                 if not ok:
2736                                     i = 0
2737                                     while True:
2738                                         newname = k + '_moved' + str(i)
2739                                         cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
2740                                             "WHERE c.relname=%s " \
2741                                             "AND a.attname=%s " \
2742                                             "AND c.oid=a.attrelid ", (self._table, newname))
2743                                         if not cr.fetchone()[0]:
2744                                             break
2745                                         i += 1
2746                                     if f_pg_notnull:
2747                                         cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2748                                     cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
2749                                     cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2750                                     cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2751                                     self.__schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2752                                         self._table, k, f_pg_type, f._type, newname)
2753
2754                             # if the field is required and hasn't got a NOT NULL constraint
2755                             if f.required and f_pg_notnull == 0:
2756                                 # set the field to the default value if any
2757                                 if k in self._defaults:
2758                                     if callable(self._defaults[k]):
2759                                         default = self._defaults[k](self, cr, 1, context)
2760                                     else:
2761                                         default = self._defaults[k]
2762
2763                                     if (default is not None):
2764                                         ss = self._columns[k]._symbol_set
2765                                         query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
2766                                         cr.execute(query, (ss[1](default),))
2767                                 # add the NOT NULL constraint
2768                                 cr.commit()
2769                                 try:
2770                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2771                                     cr.commit()
2772                                     self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
2773                                                         self._table, k)
2774                                 except Exception:
2775                                     msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
2776                                         "If you want to have it, you should update the records and execute manually:\n"\
2777                                         "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2778                                     self.__schema.warn(msg, self._table, k, self._table, k)
2779                                 cr.commit()
2780                             elif not f.required and f_pg_notnull == 1:
2781                                 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2782                                 cr.commit()
2783                                 self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2784                                                     self._table, k)
2785                             # Verify index
2786                             indexname = '%s_%s_index' % (self._table, k)
2787                             cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2788                             res2 = cr.dictfetchall()
2789                             if not res2 and f.select:
2790                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2791                                 cr.commit()
2792                                 if f._type == 'text':
2793                                     # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
2794                                     msg = "Table '%s': Adding (b-tree) index for text column '%s'."\
2795                                         "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
2796                                         " because there is a length limit for indexable btree values!\n"\
2797                                         "Use a search view instead if you simply want to make the field searchable."
2798                                     self.__schema.warn(msg, self._table, k, f._type)
2799                             if res2 and not f.select:
2800                                 cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
2801                                 cr.commit()
2802                                 msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
2803                                 self.__schema.debug(msg, self._table, k, f._type)
2804
2805                             if isinstance(f, fields.many2one):
2806                                 ref = self.pool.get(f._obj)._table
2807                                 if ref != 'ir_actions':
2808                                     cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
2809                                                 'pg_attribute as att1, pg_attribute as att2 '
2810                                             'WHERE con.conrelid = cl1.oid '
2811                                                 'AND cl1.relname = %s '
2812                                                 'AND con.confrelid = cl2.oid '
2813                                                 'AND cl2.relname = %s '
2814                                                 'AND array_lower(con.conkey, 1) = 1 '
2815                                                 'AND con.conkey[1] = att1.attnum '
2816                                                 'AND att1.attrelid = cl1.oid '
2817                                                 'AND att1.attname = %s '
2818                                                 'AND array_lower(con.confkey, 1) = 1 '
2819                                                 'AND con.confkey[1] = att2.attnum '
2820                                                 'AND att2.attrelid = cl2.oid '
2821                                                 'AND att2.attname = %s '
2822                                                 "AND con.contype = 'f'", (self._table, ref, k, 'id'))
2823                                     res2 = cr.dictfetchall()
2824                                     if res2:
2825                                         if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
2826                                             cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
2827                                             cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
2828                                             cr.commit()
2829                                             self.__schema.debug("Table '%s': column '%s': XXX",
2830                                                 self._table, k)
2831                     elif len(res) > 1:
2832                         netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
2833                     if not res:
2834                         if not isinstance(f, fields.function) or f.store:
2835                             # add the missing field
2836                             cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
2837                             cr.execute("COMMENT ON COLUMN %s.%s IS '%s'" % (self._table, k, f.string.replace("'", "''")))
2838                             self.__schema.debug("Table '%s': added column '%s' with definition=%s",
2839                                 self._table, k, get_pg_type(f)[1])
2840
2841                             # initialize it
2842                             if not create and k in self._defaults:
2843                                 if callable(self._defaults[k]):
2844                                     default = self._defaults[k](self, cr, 1, context)
2845                                 else:
2846                                     default = self._defaults[k]
2847
2848                                 ss = self._columns[k]._symbol_set
2849                                 query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
2850                                 cr.execute(query, (ss[1](default),))
2851                                 cr.commit()
2852                                 netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
2853
2854                             if isinstance(f, fields.function):
2855                                 order = 10
2856                                 if f.store is not True:
2857                                     order = f.store[f.store.keys()[0]][2]
2858                                 todo_update_store.append((order, f, k))
2859
2860                             # and add constraints if needed
2861                             if isinstance(f, fields.many2one):
2862                                 if not self.pool.get(f._obj):
2863                                     raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
2864                                 ref = self.pool.get(f._obj)._table
2865 #                                ref = f._obj.replace('.', '_')
2866                                 # ir_actions is inherited so foreign key doesn't work on it
2867                                 if ref != 'ir_actions':
2868                                     cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
2869                                     self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
2870                                         self._table, k, ref, f.ondelete)
2871                             if f.select:
2872                                 cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
2873                             if f.required:
2874                                 try:
2875                                     cr.commit()
2876                                     cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
2877                                     self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2878                                         self._table, k)
2879                                 except Exception:
2880                                     msg = "WARNING: unable to set column %s of table %s not null !\n"\
2881                                         "Try to re-run: openerp-server --update=module\n"\
2882                                         "If it doesn't work, update records and execute manually:\n"\
2883                                         "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
2884                                     self.__logger.warn(msg, k, self._table, self._table, k)
2885                             cr.commit()
2886             for order, f, k in todo_update_store:
2887                 todo_end.append((order, self._update_store, (f, k)))
2888
2889         else:
2890             cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
2891             create = not bool(cr.fetchone())
2892
2893         cr.commit()     # start a new transaction
2894
2895         for (key, con, _) in self._sql_constraints:
2896             conname = '%s_%s' % (self._table, key)
2897
2898             cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
2899             existing_constraints = cr.dictfetchall()
2900
2901             sql_actions = {
2902                 'drop': {
2903                     'execute': False,
2904                     'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
2905                     'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
2906                         self._table, conname, con),
2907                     'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
2908                     'order': 1,
2909                 },
2910                 'add': {
2911                     'execute': False,
2912                     'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
2913                     'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
2914                     'msg_err': "Table '%s': unable to add \'%s\' constraint !\n If you want to have it, you should update the records and execute manually:\n%%s" % (
2915                         self._table, con),
2916                     'order': 2,
2917                 },
2918             }
2919
2920             if not existing_constraints:
2921                 # constraint does not exists:
2922                 sql_actions['add']['execute'] = True
2923                 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2924             elif con.lower() not in [item['condef'].lower() for item in existing_constraints]:
2925                 # constraint exists but its definition has changed:
2926                 sql_actions['drop']['execute'] = True
2927                 sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
2928                 sql_actions['add']['execute'] = True
2929                 sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
2930
2931             # we need to add the constraint:
2932             sql_actions = [item for item in sql_actions.values()]
2933             sql_actions.sort(key=lambda x: x['order'])
2934             for sql_action in [action for action in sql_actions if action['execute']]:
2935                 try:
2936                     cr.execute(sql_action['query'])
2937                     cr.commit()
2938                     self.__schema.debug(sql_action['msg_ok'])
2939                 except:
2940                     self.__schema.warn(sql_action['msg_err'])
2941                     cr.rollback()
2942
2943         if create:
2944             if hasattr(self, "_sql"):
2945                 for line in self._sql.split(';'):
2946                     line2 = line.replace('\n', '').strip()
2947                     if line2:
2948                         cr.execute(line2)
2949                         cr.commit()
2950         if store_compute:
2951             self._parent_store_compute(cr)
2952             cr.commit()
2953         return todo_end
2954
2955     @classmethod
2956     def createInstance(cls, pool, cr):
2957         return cls.makeInstance(pool, cr, ['_columns', '_defaults',
2958             '_inherits', '_constraints', '_sql_constraints'])
2959
2960     def __init__(self, pool, cr):
2961         """
2962
2963         - copy the stored fields' functions in the osv_pool,
2964         - update the _columns with the fields found in ir_model_fields,
2965         - ensure there is a many2one for each _inherits'd parent,
2966         - update the children's _columns,
2967         - give a chance to each field to initialize itself.
2968
2969         """
2970         super(orm, self).__init__(pool, cr)
2971
2972         if not hasattr(self, '_log_access'):
2973             # if not access is not specify, it is the same value as _auto
2974             self._log_access = getattr(self, "_auto", True)
2975
2976         self._columns = self._columns.copy()
2977         for store_field in self._columns:
2978             f = self._columns[store_field]
2979             if hasattr(f, 'digits_change'):
2980                 f.digits_change(cr)
2981             if not isinstance(f, fields.function):
2982                 continue
2983             if not f.store:
2984                 continue
2985             if self._columns[store_field].store is True:
2986                 sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
2987             else:
2988                 sm = self._columns[store_field].store
2989             for object, aa in sm.items():
2990                 if len(aa) == 4:
2991                     (fnct, fields2, order, length) = aa
2992                 elif len(aa) == 3:
2993                     (fnct, fields2, order) = aa
2994                     length = None
2995                 else:
2996                     raise except_orm('Error',
2997                         ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
2998                 self.pool._store_function.setdefault(object, [])
2999                 ok = True
3000                 for x, y, z, e, f, l in self.pool._store_function[object]:
3001                     if (x==self._name) and (y==store_field) and (e==fields2):
3002                         if f == order:
3003                             ok = False
3004                 if ok:
3005                     self.pool._store_function[object].append( (self._name, store_field, fnct, fields2, order, length))
3006                     self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
3007
3008         for (key, _, msg) in self._sql_constraints:
3009             self.pool._sql_error[self._table+'_'+key] = msg
3010
3011         # Load manual fields
3012
3013         cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
3014         if cr.fetchone():
3015             cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
3016             for field in cr.dictfetchall():
3017                 if field['name'] in self._columns:
3018                     continue
3019                 attrs = {
3020                     'string': field['field_description'],
3021                     'required': bool(field['required']),
3022                     'readonly': bool(field['readonly']),
3023                     'domain': eval(field['domain']) if field['domain'] else None,
3024                     'size': field['size'],
3025                     'ondelete': field['on_delete'],
3026                     'translate': (field['translate']),
3027                     'manual': True,
3028                     #'select': int(field['select_level'])
3029                 }
3030
3031                 if field['ttype'] == 'selection':
3032                     self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
3033                 elif field['ttype'] == 'reference':
3034                     self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
3035                 elif field['ttype'] == 'many2one':
3036                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
3037                 elif field['ttype'] == 'one2many':
3038                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
3039                 elif field['ttype'] == 'many2many':
3040                     _rel1 = field['relation'].replace('.', '_')
3041                     _rel2 = field['model'].replace('.', '_')
3042                     _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
3043                     self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
3044                 else:
3045                     self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
3046         self._inherits_check()
3047         self._inherits_reload()
3048         if not self._sequence:
3049             self._sequence = self._table + '_id_seq'
3050         for k in self._defaults:
3051             assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
3052         for f in self._columns:
3053             self._columns[f].restart()
3054
3055     __init__.__doc__ = orm_template.__init__.__doc__ + __init__.__doc__
3056
3057     #
3058     # Update objects that uses this one to update their _inherits fields
3059     #
3060
3061     def _inherits_reload_src(self):
3062         for obj in self.pool.obj_pool.values():
3063             if self._name in obj._inherits:
3064                 obj._inherits_reload()
3065
3066     def _inherits_reload(self):
3067         res = {}
3068         for table in self._inherits:
3069             res.update(self.pool.get(table)._inherit_fields)
3070             for col in self.pool.get(table)._columns.keys():
3071                 res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
3072             for col in self.pool.get(table)._inherit_fields.keys():
3073                 res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
3074         self._inherit_fields = res
3075         self._inherits_reload_src()
3076
3077     def _inherits_check(self):
3078         for table, field_name in self._inherits.items():
3079             if field_name not in self._columns:
3080                 logging.getLogger('init').info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.' % (field_name, self._name))
3081                 self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
3082                                                              required=True, ondelete="cascade")
3083             elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() != "cascade":
3084                 logging.getLogger('init').warning('Field definition for _inherits reference "%s" in "%s" must be marked as "required" with ondelete="cascade", forcing it.' % (field_name, self._name))
3085                 self._columns[field_name].required = True
3086                 self._columns[field_name].ondelete = "cascade"
3087
3088     #def __getattr__(self, name):
3089     #    """
3090     #    Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
3091     #    (though inherits doesn't use Python inheritance).
3092     #    Handles translating between local ids and remote ids.
3093     #    Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
3094     #                 when you have inherits.
3095     #    """
3096     #    for model, field in self._inherits.iteritems():
3097     #        proxy = self.pool.get(model)
3098     #        if hasattr(proxy, name):
3099     #            attribute = getattr(proxy, name)
3100     #            if not hasattr(attribute, '__call__'):
3101     #                return attribute
3102     #            break
3103     #    else:
3104     #        return super(orm, self).__getattr__(name)
3105
3106     #    def _proxy(cr, uid, ids, *args, **kwargs):
3107     #        objects = self.browse(cr, uid, ids, kwargs.get('context', None))
3108     #        lst = [obj[field].id for obj in objects if obj[field]]
3109     #        return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
3110
3111     #    return _proxy
3112
3113
3114     def fields_get(self, cr, user, fields=None, context=None):
3115         """
3116         Get the description of list of fields
3117
3118         :param cr: database cursor
3119         :param user: current user id
3120         :param fields: list of fields
3121         :param context: context arguments, like lang, time zone
3122         :return: dictionary of field dictionaries, each one describing a field of the business object
3123         :raise AccessError: * if user has no create/write rights on the requested object
3124
3125         """
3126         ira = self.pool.get('ir.model.access')
3127         write_access = ira.check(cr, user, self._name, 'write', raise_exception=False, context=context) or \
3128                        ira.check(cr, user, self._name, 'create', raise_exception=False, context=context)
3129         return super(orm, self).fields_get(cr, user, fields, context, write_access)
3130
3131     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
3132         if not context:
3133             context = {}
3134         self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
3135         if not fields:
3136             fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
3137         if isinstance(ids, (int, long)):
3138             select = [ids]
3139         else:
3140             select = ids
3141         select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
3142         result = self._read_flat(cr, user, select, fields, context, load)
3143
3144         for r in result:
3145             for key, v in r.items():
3146                 if v is None:
3147                     r[key] = False
3148
3149         if isinstance(ids, (int, long, dict)):
3150             return result and result[0] or False
3151         return result
3152
3153     def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
3154         if not context:
3155             context = {}
3156         if not ids:
3157             return []
3158         if fields_to_read == None:
3159             fields_to_read = self._columns.keys()
3160
3161         # Construct a clause for the security rules.
3162         # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3163         # or will at least contain self._table.
3164         rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3165
3166         # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
3167         fields_pre = [f for f in fields_to_read if
3168                            f == self.CONCURRENCY_CHECK_FIELD
3169                         or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3170                      ] + self._inherits.values()
3171
3172         res = []
3173         if len(fields_pre):
3174             def convert_field(f):
3175                 f_qual = "%s.%s" % (self._table, f) # need fully-qualified references in case len(tables) > 1
3176                 if f in ('create_date', 'write_date'):
3177                     return "date_trunc('second', %s) as %s" % (f_qual, f)
3178                 if f == self.CONCURRENCY_CHECK_FIELD:
3179                     if self._log_access:
3180                         return "COALESCE(%s.write_date, %s.create_date, now())::timestamp AS %s" % (self._table, self._table, f,)
3181                     return "now()::timestamp AS %s" % (f,)
3182                 if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
3183                     return 'length(%s) as "%s"' % (f_qual, f)
3184                 return f_qual
3185
3186             fields_pre2 = map(convert_field, fields_pre)
3187             order_by = self._parent_order or self._order
3188             select_fields = ','.join(fields_pre2 + [self._table + '.id'])
3189             query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3190             if rule_clause:
3191                 query += " AND " + (' OR '.join(rule_clause))
3192             query += " ORDER BY " + order_by
3193             for sub_ids in cr.split_for_in_conditions(ids):
3194                 if rule_clause:
3195                     cr.execute(query, [tuple(sub_ids)] + rule_params)
3196                     if cr.rowcount != len(sub_ids):
3197                         raise except_orm(_('AccessError'),
3198                                          _('Operation prohibited by access rules, or performed on an already deleted document (Operation: read, Document type: %s).')
3199                                          % (self._description,))
3200                 else:
3201                     cr.execute(query, (tuple(sub_ids),))
3202                 res.extend(cr.dictfetchall())
3203         else:
3204             res = map(lambda x: {'id': x}, ids)
3205
3206         for f in fields_pre:
3207             if f == self.CONCURRENCY_CHECK_FIELD:
3208                 continue
3209             if self._columns[f].translate:
3210                 ids = [x['id'] for x in res]
3211                 #TODO: optimize out of this loop
3212                 res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids)
3213                 for r in res:
3214                     r[f] = res_trans.get(r['id'], False) or r[f]
3215
3216         for table in self._inherits:
3217             col = self._inherits[table]
3218             cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
3219             if not cols:
3220                 continue
3221             res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3222
3223             res3 = {}
3224             for r in res2:
3225                 res3[r['id']] = r
3226                 del r['id']
3227
3228             for record in res:
3229                 if not record[col]: # if the record is deleted from _inherits table?
3230                     continue
3231                 record.update(res3[record[col]])
3232                 if col not in fields_to_read:
3233                     del record[col]
3234
3235         # all fields which need to be post-processed by a simple function (symbol_get)
3236         fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
3237         if fields_post:
3238             for r in res:
3239                 for f in fields_post:
3240                     r[f] = self._columns[f]._symbol_get(r[f])
3241         ids = [x['id'] for x in res]
3242
3243         # all non inherited fields for which the attribute whose name is in load is False
3244         fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
3245
3246         # Compute POST fields
3247         todo = {}
3248         for f in fields_post:
3249             todo.setdefault(self._columns[f]._multi, [])
3250             todo[self._columns[f]._multi].append(f)
3251         for key, val in todo.items():
3252             if key:
3253                 res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3254                 assert res2 is not None, \
3255                     'The function field "%s" on the "%s" model returned None\n' \
3256                     '(a dictionary was expected).' % (val[0], self._name)
3257                 for pos in val:
3258                     for record in res:
3259                         if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
3260                         multi_fields = res2.get(record['id'],{})
3261                         if multi_fields:
3262                             record[pos] = multi_fields.get(pos,[])
3263             else:
3264                 for f in val:
3265                     res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3266                     for record in res:
3267                         if res2:
3268                             record[f] = res2[record['id']]
3269                         else:
3270                             record[f] = []
3271         readonly = None
3272         for vals in res:
3273             for field in vals.copy():
3274                 fobj = None
3275                 if field in self._columns:
3276                     fobj = self._columns[field]
3277
3278                 if not fobj:
3279                     continue
3280                 groups = fobj.read
3281                 if groups:
3282                     edit = False
3283                     for group in groups:
3284                         module = group.split(".")[0]
3285                         grp = group.split(".")[1]
3286                         cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s",  \
3287                                    (grp, module, 'res.groups', user))
3288                         readonly = cr.fetchall()
3289                         if readonly[0][0] >= 1:
3290                             edit = True
3291                             break
3292                         elif readonly[0][0] == 0:
3293                             edit = False
3294                         else:
3295                             edit = False
3296
3297                     if not edit:
3298                         if type(vals[field]) == type([]):
3299                             vals[field] = []
3300                         elif type(vals[field]) == type(0.0):
3301                             vals[field] = 0
3302                         elif type(vals[field]) == type(''):
3303                             vals[field] = '=No Permission='
3304                         else:
3305                             vals[field] = False
3306         return res
3307
3308     def perm_read(self, cr, user, ids, context=None, details=True):
3309         """
3310         Returns some metadata about the given records.
3311
3312         :param details: if True, \*_uid fields are replaced with the name of the user
3313         :return: list of ownership dictionaries for each requested record
3314         :rtype: list of dictionaries with the following keys:
3315
3316                     * id: object id
3317                     * create_uid: user who created the record
3318                     * create_date: date when the record was created
3319                     * write_uid: last user who changed the record
3320                     * write_date: date of the last change to the record
3321                     * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
3322         """
3323         if not context:
3324             context = {}
3325         if not ids:
3326             return []
3327         fields = ''
3328         uniq = isinstance(ids, (int, long))
3329         if uniq:
3330             ids = [ids]
3331         fields = ['id']
3332         if self._log_access:
3333             fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3334         quoted_table = '"%s"' % self._table
3335         fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3336         query = '''SELECT %s, __imd.module, __imd.name
3337                    FROM %s LEFT JOIN ir_model_data __imd
3338                        ON (__imd.model = %%s and __imd.res_id = %s.id)
3339                    WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3340         cr.execute(query, (self._name, tuple(ids)))
3341         res = cr.dictfetchall()
3342         for r in res:
3343             for key in r:
3344                 r[key] = r[key] or False
3345                 if details and key in ('write_uid', 'create_uid') and r[key]:
3346                     try:
3347                         r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3348                     except Exception:
3349                         pass # Leave the numeric uid there
3350             r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3351             del r['name'], r['module']
3352         if uniq:
3353             return res[ids[0]]
3354         return res
3355
3356     def _check_concurrency(self, cr, ids, context):
3357         if not context:
3358             return
3359         if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
3360             return
3361         check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, now())::timestamp)"
3362         for sub_ids in cr.split_for_in_conditions(ids):
3363             ids_to_check = []
3364             for id in sub_ids:
3365                 id_ref = "%s,%s" % (self._name, id)
3366                 update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3367                 if update_date:
3368                     ids_to_check.extend([id, update_date])
3369             if not ids_to_check:
3370                 continue
3371             cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3372             res = cr.fetchone()
3373             if res:
3374                 # mention the first one only to keep the error message readable
3375                 raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
3376
3377     def check_access_rule(self, cr, uid, ids, operation, context=None):
3378         """Verifies that the operation given by ``operation`` is allowed for the user
3379            according to ir.rules.
3380
3381            :param operation: one of ``write``, ``unlink``
3382            :raise except_orm: * if current ir.rules do not permit this operation.
3383            :return: None if the operation is allowed
3384         """
3385         where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3386         if where_clause:
3387             where_clause = ' and ' + ' and '.join(where_clause)
3388             for sub_ids in cr.split_for_in_conditions(ids):
3389                 cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3390                            ' WHERE ' + self._table + '.id IN %s' + where_clause,
3391                            [sub_ids] + where_params)
3392                 if cr.rowcount != len(sub_ids):
3393                     raise except_orm(_('AccessError'),
3394                                      _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3395                                      % (operation, self._description))
3396
3397     def unlink(self, cr, uid, ids, context=None):
3398         """
3399         Delete records with given ids
3400
3401         :param cr: database cursor
3402         :param uid: current user id
3403         :param ids: id or list of ids
3404         :param context: (optional) context arguments, like lang, time zone
3405         :return: True
3406         :raise AccessError: * if user has no unlink rights on the requested object
3407                             * if user tries to bypass access rules for unlink on the requested object
3408         :raise UserError: if the record is default property for other records
3409
3410         """
3411         if not ids:
3412             return True
3413         if isinstance(ids, (int, long)):
3414             ids = [ids]
3415
3416         result_store = self._store_get_values(cr, uid, ids, None, context)
3417
3418         self._check_concurrency(cr, ids, context)
3419
3420         self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context)
3421
3422         properties = self.pool.get('ir.property')
3423         domain = [('res_id', '=', False),
3424                   ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
3425                  ]
3426         if properties.search(cr, uid, domain, context=context):
3427             raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3428
3429         wf_service = netsvc.LocalService("workflow")
3430         for oid in ids:
3431             wf_service.trg_delete(uid, self._name, oid, cr)
3432
3433
3434         self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3435         pool_model_data = self.pool.get('ir.model.data')
3436         ir_values_obj = self.pool.get('ir.values')
3437         for sub_ids in cr.split_for_in_conditions(ids):
3438             cr.execute('delete from ' + self._table + ' ' \
3439                        'where id IN %s', (sub_ids,))
3440
3441             # Removing the ir_model_data reference if the record being deleted is a record created by xml/csv file,
3442             # as these are not connected with real database foreign keys, and would be dangling references.
3443             # Step 1. Calling unlink of ir_model_data only for the affected IDS.
3444             referenced_ids = pool_model_data.search(cr, uid, [('res_id','in',list(sub_ids)),('model','=',self._name)], context=context)
3445             # Step 2. Marching towards the real deletion of referenced records
3446             pool_model_data.unlink(cr, uid, referenced_ids, context=context)
3447
3448             # For the same reason, removing the record relevant to ir_values
3449             ir_value_ids = ir_values_obj.search(cr, uid,
3450                     ['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
3451                     context=context)
3452             if ir_value_ids:
3453                 ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
3454
3455         for order, object, store_ids, fields in result_store:
3456             if object != self._name:
3457                 obj = self.pool.get(object)
3458                 cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
3459                 rids = map(lambda x: x[0], cr.fetchall())
3460                 if rids:
3461                     obj._store_set_values(cr, uid, rids, fields, context)
3462
3463         return True
3464
3465     #
3466     # TODO: Validate
3467     #
3468     def write(self, cr, user, ids, vals, context=None):
3469         """
3470         Update records with given ids with the given field values
3471
3472         :param cr: database cursor
3473         :param user: current user id
3474         :type user: integer
3475         :param ids: object id or list of object ids to update according to **vals**
3476         :param vals: field values to update, e.g {'field_name': new_field_value, ...}
3477         :type vals: dictionary
3478         :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3479         :type context: dictionary
3480         :return: True
3481         :raise AccessError: * if user has no write rights on the requested object
3482                             * if user tries to bypass access rules for write on the requested object
3483         :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3484         :raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
3485
3486         **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
3487
3488             + For a many2many field, a list of tuples is expected.
3489               Here is the list of tuple that are accepted, with the corresponding semantics ::
3490
3491                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
3492                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
3493                  (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
3494                  (3, ID)                cut the link to the linked record with id = ID (delete the relationship between the two objects but does not delete the target object itself)
3495                  (4, ID)                link to existing record with id = ID (adds a relationship)
3496                  (5)                    unlink all (like using (3,ID) for all linked records)
3497                  (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
3498
3499                  Example:
3500                     [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
3501
3502             + For a one2many field, a lits of tuples is expected.
3503               Here is the list of tuple that are accepted, with the corresponding semantics ::
3504
3505                  (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
3506                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
3507                  (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
3508
3509                  Example:
3510                     [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
3511
3512             + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
3513             + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
3514
3515         """
3516         readonly = None
3517         for field in vals.copy():
3518             fobj = None
3519             if field in self._columns:
3520                 fobj = self._columns[field]
3521             elif field in self._inherit_fields:
3522                 fobj = self._inherit_fields[field][2]
3523             if not fobj:
3524                 continue
3525             groups = fobj.write
3526
3527             if groups:
3528                 edit = False
3529                 for group in groups:
3530                     module = group.split(".")[0]
3531                     grp = group.split(".")[1]
3532                     cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s", \
3533                                (grp, module, 'res.groups', user))
3534                     readonly = cr.fetchall()
3535                     if readonly[0][0] >= 1:
3536                         edit = True
3537                         break
3538                     elif readonly[0][0] == 0:
3539                         edit = False
3540                     else:
3541                         edit = False
3542
3543                 if not edit:
3544                     vals.pop(field)
3545
3546         if not context:
3547             context = {}
3548         if not ids:
3549             return True
3550         if isinstance(ids, (int, long)):
3551             ids = [ids]
3552
3553         self._check_concurrency(cr, ids, context)
3554         self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context)
3555
3556         result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
3557
3558         # No direct update of parent_left/right
3559         vals.pop('parent_left', None)
3560         vals.pop('parent_right', None)
3561
3562         parents_changed = []
3563         parent_order = self._parent_order or self._order
3564         if self._parent_store and (self._parent_name in vals):
3565             # The parent_left/right computation may take up to
3566             # 5 seconds. No need to recompute the values if the
3567             # parent is the same.
3568             # Note: to respect parent_order, nodes must be processed in
3569             # order, so ``parents_changed`` must be ordered properly.
3570             parent_val = vals[self._parent_name]
3571             if parent_val:
3572                 query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL) ORDER BY %s" % \
3573                                 (self._table, self._parent_name, self._parent_name, parent_order)
3574                 cr.execute(query, (tuple(ids), parent_val))
3575             else:
3576                 query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \
3577                                 (self._table, self._parent_name, parent_order)
3578                 cr.execute(query, (tuple(ids),))
3579             parents_changed = map(operator.itemgetter(0), cr.fetchall())
3580
3581         upd0 = []
3582         upd1 = []
3583         upd_todo = []
3584         updend = []
3585         direct = []
3586         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
3587         for field in vals:
3588             if field in self._columns:
3589                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
3590                     if (not totranslate) or not self._columns[field].translate:
3591                         upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
3592                         upd1.append(self._columns[field]._symbol_set[1](vals[field]))
3593                     direct.append(field)
3594                 else:
3595                     upd_todo.append(field)
3596             else:
3597                 updend.append(field)
3598             if field in self._columns \
3599                     and hasattr(self._columns[field], 'selection') \
3600                     and vals[field]:
3601                 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3602
3603         if self._log_access:
3604             upd0.append('write_uid=%s')
3605             upd0.append('write_date=now()')
3606             upd1.append(user)
3607
3608         if len(upd0):
3609             self.check_access_rule(cr, user, ids, 'write', context=context)
3610             for sub_ids in cr.split_for_in_conditions(ids):
3611                 cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
3612                            'where id IN %s', upd1 + [sub_ids])
3613                 if cr.rowcount != len(sub_ids):
3614                     raise except_orm(_('AccessError'),
3615                                      _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
3616
3617             if totranslate:
3618                 # TODO: optimize
3619                 for f in direct:
3620                     if self._columns[f].translate:
3621                         src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
3622                         if not src_trans:
3623                             src_trans = vals[f]
3624                             # Inserting value to DB
3625                             self.write(cr, user, ids, {f: vals[f]})
3626                         self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
3627
3628
3629         # call the 'set' method of fields which are not classic_write
3630         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3631
3632         # default element in context must be removed when call a one2many or many2many
3633         rel_context = context.copy()
3634         for c in context.items():
3635             if c[0].startswith('default_'):
3636                 del rel_context[c[0]]
3637
3638         for field in upd_todo:
3639             for id in ids:
3640                 result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
3641
3642         for table in self._inherits:
3643             col = self._inherits[table]
3644             nids = []
3645             for sub_ids in cr.split_for_in_conditions(ids):
3646                 cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
3647                            'where id IN %s', (sub_ids,))
3648                 nids.extend([x[0] for x in cr.fetchall()])
3649
3650             v = {}
3651             for val in updend:
3652                 if self._inherit_fields[val][0] == table:
3653                     v[val] = vals[val]
3654             if v:
3655                 self.pool.get(table).write(cr, user, nids, v, context)
3656
3657         self._validate(cr, user, ids, context)
3658
3659         # TODO: use _order to set dest at the right position and not first node of parent
3660         # We can't defer parent_store computation because the stored function
3661         # fields that are computer may refer (directly or indirectly) to
3662         # parent_left/right (via a child_of domain)
3663         if parents_changed:
3664             if self.pool._init:
3665                 self.pool._init_parent[self._name] = True
3666             else:
3667                 order = self._parent_order or self._order
3668                 parent_val = vals[self._parent_name]
3669                 if parent_val:
3670                     clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
3671                 else:
3672                     clause, params = '%s IS NULL' % (self._parent_name,), ()
3673
3674                 for id in parents_changed:
3675                     cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
3676                     pleft, pright = cr.fetchone()
3677                     distance = pright - pleft + 1
3678
3679                     # Positions of current siblings, to locate proper insertion point;
3680                     # this can _not_ be fetched outside the loop, as it needs to be refreshed
3681                     # after each update, in case several nodes are sequentially inserted one
3682                     # next to the other (i.e computed incrementally)
3683                     cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, parent_order), params)
3684                     parents = cr.fetchall()
3685
3686                     # Find Position of the element
3687                     position = None
3688                     for (parent_pright, parent_id) in parents:
3689                         if parent_id == id:
3690                             break
3691                         position = parent_pright + 1
3692
3693                     # It's the first node of the parent
3694                     if not position:
3695                         if not parent_val:
3696                             position = 1
3697                         else:
3698                             cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
3699                             position = cr.fetchone()[0] + 1
3700
3701                     if pleft < position <= pright:
3702                         raise except_orm(_('UserError'), _('Recursivity Detected.'))
3703
3704                     if pleft < position:
3705                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3706                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3707                         cr.execute('update '+self._table+' set parent_left=parent_left+%s, parent_right=parent_right+%s where parent_left>=%s and parent_left<%s', (position-pleft, position-pleft, pleft, pright))
3708                     else:
3709                         cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
3710                         cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
3711                         cr.execute('update '+self._table+' set parent_left=parent_left-%s, parent_right=parent_right-%s where parent_left>=%s and parent_left<%s', (pleft-position+distance, pleft-position+distance, pleft+distance, pright+distance))
3712
3713         result += self._store_get_values(cr, user, ids, vals.keys(), context)
3714         result.sort()
3715
3716         done = {}
3717         for order, object, ids_to_update, fields_to_recompute in result:
3718             key = (object, tuple(fields_to_recompute))
3719             done.setdefault(key, {})
3720             # avoid to do several times the same computation
3721             todo = []
3722             for id in ids_to_update:
3723                 if id not in done[key]:
3724                     done[key][id] = True
3725                     todo.append(id)
3726             self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
3727
3728         wf_service = netsvc.LocalService("workflow")
3729         for id in ids:
3730             wf_service.trg_write(user, self._name, id, cr)
3731         return True
3732
3733     #
3734     # TODO: Should set perm to user.xxx
3735     #
3736     def create(self, cr, user, vals, context=None):
3737         """
3738         Create a new record for the model.
3739
3740         The values for the new record are initialized using the ``vals``
3741         argument, and if necessary the result of ``default_get()``.
3742
3743         :param cr: database cursor
3744         :param user: current user id
3745         :type user: integer
3746         :param vals: field values for new record, e.g {'field_name': field_value, ...}
3747         :type vals: dictionary
3748         :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
3749         :type context: dictionary
3750         :return: id of new record created
3751         :raise AccessError: * if user has no create rights on the requested object
3752                             * if user tries to bypass access rules for create on the requested object
3753         :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
3754         :raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
3755
3756         **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
3757         Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
3758         to specify them.
3759
3760         """
3761         if not context:
3762             context = {}
3763         self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context)
3764
3765         vals = self._add_missing_default_values(cr, user, vals, context)
3766
3767         tocreate = {}
3768         for v in self._inherits:
3769             if self._inherits[v] not in vals:
3770                 tocreate[v] = {}
3771             else:
3772                 tocreate[v] = {'id': vals[self._inherits[v]]}
3773         (upd0, upd1, upd2) = ('', '', [])
3774         upd_todo = []
3775         for v in vals.keys():
3776             if v in self._inherit_fields:
3777                 (table, col, col_detail) = self._inherit_fields[v]
3778                 tocreate[table][v] = vals[v]
3779                 del vals[v]
3780             else:
3781                 if (v not in self._inherit_fields) and (v not in self._columns):
3782                     del vals[v]
3783
3784         # Try-except added to filter the creation of those records whose filds are readonly.
3785         # Example : any dashboard which has all the fields readonly.(due to Views(database views))
3786         try:
3787             cr.execute("SELECT nextval('"+self._sequence+"')")
3788         except:
3789             raise except_orm(_('UserError'),
3790                         _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
3791
3792         id_new = cr.fetchone()[0]
3793         for table in tocreate:
3794             if self._inherits[table] in vals:
3795                 del vals[self._inherits[table]]
3796
3797             record_id = tocreate[table].pop('id', None)
3798
3799             if record_id is None or not record_id:
3800                 record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
3801             else:
3802                 self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
3803
3804             upd0 += ',' + self._inherits[table]
3805             upd1 += ',%s'
3806             upd2.append(record_id)
3807
3808         #Start : Set bool fields to be False if they are not touched(to make search more powerful)
3809         bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
3810
3811         for bool_field in bool_fields:
3812             if bool_field not in vals:
3813                 vals[bool_field] = False
3814         #End
3815         for field in vals.copy():
3816             fobj = None
3817             if field in self._columns:
3818                 fobj = self._columns[field]
3819             else:
3820                 fobj = self._inherit_fields[field][2]
3821             if not fobj:
3822                 continue
3823             groups = fobj.write
3824             if groups:
3825                 edit = False
3826                 for group in groups:
3827                     module = group.split(".")[0]
3828                     grp = group.split(".")[1]
3829                     cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \
3830                                (grp, module, 'res.groups', user))
3831                     readonly = cr.fetchall()
3832                     if readonly[0][0] >= 1:
3833                         edit = True
3834                         break
3835                     elif readonly[0][0] == 0:
3836                         edit = False
3837                     else:
3838                         edit = False
3839
3840                 if not edit:
3841                     vals.pop(field)
3842         for field in vals:
3843             if self._columns[field]._classic_write:
3844                 upd0 = upd0 + ',"' + field + '"'
3845                 upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
3846                 upd2.append(self._columns[field]._symbol_set[1](vals[field]))
3847             else:
3848                 if not isinstance(self._columns[field], fields.related):
3849                     upd_todo.append(field)
3850             if field in self._columns \
3851                     and hasattr(self._columns[field], 'selection') \
3852                     and vals[field]:
3853                 self._check_selection_field_value(cr, user, field, vals[field], context=context)
3854         if self._log_access:
3855             upd0 += ',create_uid,create_date'
3856             upd1 += ',%s,now()'
3857             upd2.append(user)
3858         cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
3859         self.check_access_rule(cr, user, [id_new], 'create', context=context)
3860         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
3861
3862         if self._parent_store and not context.get('defer_parent_store_computation'):
3863             if self.pool._init:
3864                 self.pool._init_parent[self._name] = True
3865             else:
3866                 parent = vals.get(self._parent_name, False)
3867                 if parent:
3868                     cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
3869                     pleft_old = None
3870                     result_p = cr.fetchall()
3871                     for (pleft,) in result_p:
3872                         if not pleft:
3873                             break
3874                         pleft_old = pleft
3875                     if not pleft_old:
3876                         cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
3877                         pleft_old = cr.fetchone()[0]
3878                     pleft = pleft_old
3879                 else:
3880                     cr.execute('select max(parent_right) from '+self._table)
3881                     pleft = cr.fetchone()[0] or 0
3882                 cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
3883                 cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
3884                 cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
3885
3886         # default element in context must be remove when call a one2many or many2many
3887         rel_context = context.copy()
3888         for c in context.items():
3889             if c[0].startswith('default_'):
3890                 del rel_context[c[0]]
3891
3892         result = []
3893         for field in upd_todo:
3894             result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
3895         self._validate(cr, user, [id_new], context)
3896
3897         if not context.get('no_store_function', False):
3898             result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
3899             result.sort()
3900             done = []
3901             for order, object, ids, fields2 in result:
3902                 if not (object, ids, fields2) in done:
3903                     self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
3904                     done.append((object, ids, fields2))
3905
3906         if self._log_create and not (context and context.get('no_store_function', False)):
3907             message = self._description + \
3908                 " '" + \
3909                 self.name_get(cr, user, [id_new], context=context)[0][1] + \
3910                 "' " + _("created.")
3911             self.log(cr, user, id_new, message, True, context=context)
3912         wf_service = netsvc.LocalService("workflow")
3913         wf_service.trg_create(user, self._name, id_new, cr)
3914         return id_new
3915
3916     def _store_get_values(self, cr, uid, ids, fields, context):
3917         """Returns an ordered list of fields.functions to call due to
3918            an update operation on ``fields`` of records with ``ids``,
3919            obtained by calling the 'store' functions of these fields,
3920            as setup by their 'store' attribute.
3921
3922            :return: [(priority, model_name, [record_ids,], [function_fields,])]
3923         """
3924         # FIXME: rewrite, cleanup, use real variable names
3925         # e.g.: http://pastie.org/1222060
3926         result = {}
3927         fncts = self.pool._store_function.get(self._name, [])
3928         for fnct in range(len(fncts)):
3929             if fncts[fnct][3]:
3930                 ok = False
3931                 if not fields:
3932                     ok = True
3933                 for f in (fields or []):
3934                     if f in fncts[fnct][3]:
3935                         ok = True
3936                         break
3937                 if not ok:
3938                     continue
3939
3940             result.setdefault(fncts[fnct][0], {})
3941
3942             # uid == 1 for accessing objects having rules defined on store fields
3943             ids2 = fncts[fnct][2](self, cr, 1, ids, context)
3944             for id in filter(None, ids2):
3945                 result[fncts[fnct][0]].setdefault(id, [])
3946                 result[fncts[fnct][0]][id].append(fnct)
3947         dict = {}
3948         for object in result:
3949             k2 = {}
3950             for id, fnct in result[object].items():
3951                 k2.setdefault(tuple(fnct), [])
3952                 k2[tuple(fnct)].append(id)
3953             for fnct, id in k2.items():
3954                 dict.setdefault(fncts[fnct[0]][4], [])
3955                 dict[fncts[fnct[0]][4]].append((fncts[fnct[0]][4], object, id, map(lambda x: fncts[x][1], fnct)))
3956         result2 = []
3957         tmp = dict.keys()
3958         tmp.sort()
3959         for k in tmp:
3960             result2 += dict[k]
3961         return result2
3962
3963     def _store_set_values(self, cr, uid, ids, fields, context):
3964         """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
3965            respecting ``multi`` attributes), and stores the resulting values in the database directly."""
3966         if not ids:
3967             return True
3968         field_flag = False
3969         field_dict = {}
3970         if self._log_access:
3971             cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
3972             res = cr.fetchall()
3973             for r in res:
3974                 if r[1]:
3975                     field_dict.setdefault(r[0], [])
3976                     res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
3977                     write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
3978                     for i in self.pool._store_function.get(self._name, []):
3979                         if i[5]:
3980                             up_write_date = write_date + datetime.timedelta(hours=i[5])
3981                             if datetime.datetime.now() < up_write_date:
3982                                 if i[1] in fields:
3983                                     field_dict[r[0]].append(i[1])
3984                                     if not field_flag:
3985                                         field_flag = True
3986         todo = {}
3987         keys = []
3988         for f in fields:
3989             if self._columns[f]._multi not in keys:
3990                 keys.append(self._columns[f]._multi)
3991             todo.setdefault(self._columns[f]._multi, [])
3992             todo[self._columns[f]._multi].append(f)
3993         for key in keys:
3994             val = todo[key]
3995             if key:
3996                 # uid == 1 for accessing objects having rules defined on store fields
3997                 result = self._columns[val[0]].get(cr, self, ids, val, 1, context=context)
3998                 for id, value in result.items():
3999                     if field_flag:
4000                         for f in value.keys():
4001                             if f in field_dict[id]:
4002                                 value.pop(f)
4003                     upd0 = []
4004                     upd1 = []
4005                     for v in value:
4006                         if v not in val:
4007                             continue
4008                         if self._columns[v]._type in ('many2one', 'one2one'):
4009                             try:
4010                                 value[v] = value[v][0]
4011                             except:
4012                                 pass
4013                         upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
4014                         upd1.append(self._columns[v]._symbol_set[1](value[v]))
4015                     upd1.append(id)
4016                     if upd0 and upd1:
4017                         cr.execute('update "' + self._table + '" set ' + \
4018                             ','.join(upd0) + ' where id = %s', upd1)
4019
4020             else:
4021                 for f in val:
4022                     # uid == 1 for accessing objects having rules defined on store fields
4023                     result = self._columns[f].get(cr, self, ids, f, 1, context=context)
4024                     for r in result.keys():
4025                         if field_flag:
4026                             if r in field_dict.keys():
4027                                 if f in field_dict[r]:
4028                                     result.pop(r)
4029                     for id, value in result.items():
4030                         if self._columns[f]._type in ('many2one', 'one2one'):
4031                             try:
4032                                 value = value[0]
4033                             except:
4034                                 pass
4035                         cr.execute('update "' + self._table + '" set ' + \
4036                             '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
4037         return True
4038
4039     #
4040     # TODO: Validate
4041     #
4042     def perm_write(self, cr, user, ids, fields, context=None):
4043         raise NotImplementedError(_('This method does not exist anymore'))
4044
4045     # TODO: ameliorer avec NULL
4046     def _where_calc(self, cr, user, domain, active_test=True, context=None):
4047         """Computes the WHERE clause needed to implement an OpenERP domain.
4048         :param domain: the domain to compute
4049         :type domain: list
4050         :param active_test: whether the default filtering of records with ``active``
4051                             field set to ``False`` should be applied.
4052         :return: the query expressing the given domain as provided in domain
4053         :rtype: osv.query.Query
4054         """
4055         if not context:
4056             context = {}
4057         domain = domain[:]
4058         # if the object has a field named 'active', filter out all inactive
4059         # records unless they were explicitely asked for
4060         if 'active' in self._columns and (active_test and context.get('active_test', True)):
4061             if domain:
4062                 active_in_args = False
4063                 for a in domain:
4064                     if a[0] == 'active':
4065                         active_in_args = True
4066                 if not active_in_args:
4067                     domain.insert(0, ('active', '=', 1))
4068             else:
4069                 domain = [('active', '=', 1)]
4070
4071         if domain:
4072             import expression
4073             e = expression.expression(domain)
4074             e.parse(cr, user, self, context)
4075             tables = e.get_tables()
4076             where_clause, where_params = e.to_sql()
4077             where_clause = where_clause and [where_clause] or []
4078         else:
4079             where_clause, where_params, tables = [], [], ['"%s"' % self._table]
4080
4081         return Query(tables, where_clause, where_params)
4082
4083     def _check_qorder(self, word):
4084         if not regex_order.match(word):
4085             raise except_orm(_('AccessError'), _('Invalid "order" specified. A valid "order" specification is a comma-separated list of valid field names (optionally followed by asc/desc for the direction)'))
4086         return True
4087
4088     def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
4089         """Add what's missing in ``query`` to implement all appropriate ir.rules
4090           (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
4091
4092            :param query: the current query object
4093         """
4094         def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
4095             if added_clause:
4096                 if parent_model and child_object:
4097                     # as inherited rules are being applied, we need to add the missing JOIN
4098                     # to reach the parent table (if it was not JOINed yet in the query)
4099                     child_object._inherits_join_add(parent_model, query)
4100                 query.where_clause += added_clause
4101                 query.where_clause_params += added_params
4102                 for table in added_tables:
4103                     if table not in query.tables:
4104                         query.tables.append(table)
4105                 return True
4106             return False
4107
4108         # apply main rules on the object
4109         rule_obj = self.pool.get('ir.rule')
4110         apply_rule(*rule_obj.domain_get(cr, uid, self._name, mode, context=context))
4111
4112         # apply ir.rules from the parents (through _inherits)
4113         for inherited_model in self._inherits:
4114             kwargs = dict(parent_model=inherited_model, child_object=self) #workaround for python2.5
4115             apply_rule(*rule_obj.domain_get(cr, uid, inherited_model, mode, context=context), **kwargs)
4116
4117     def _generate_m2o_order_by(self, order_field, query):
4118         """
4119         Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
4120         either native m2o fields or function/related fields that are stored, including
4121         intermediate JOINs for inheritance if required.
4122
4123         :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
4124         """
4125         if order_field not in self._columns and order_field in self._inherit_fields:
4126             # also add missing joins for reaching the table containing the m2o field
4127             qualified_field = self._inherits_join_calc(order_field, query)
4128             order_field_column = self._inherit_fields[order_field][2]
4129         else:
4130             qualified_field = '"%s"."%s"' % (self._table, order_field)
4131             order_field_column = self._columns[order_field]
4132
4133         assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
4134         if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
4135             logging.getLogger('orm.search').debug("Many2one function/related fields must be stored " \
4136                                                   "to be used as ordering fields! Ignoring sorting for %s.%s",
4137                                                   self._name, order_field)
4138             return
4139
4140         # figure out the applicable order_by for the m2o
4141         dest_model = self.pool.get(order_field_column._obj)
4142         m2o_order = dest_model._order
4143         if not regex_order.match(m2o_order):
4144             # _order is complex, can't use it here, so we default to _rec_name
4145             m2o_order = dest_model._rec_name
4146         else:
4147             # extract the field names, to be able to qualify them and add desc/asc
4148             m2o_order_list = []
4149             for order_part in m2o_order.split(","):
4150                 m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
4151             m2o_order = m2o_order_list
4152
4153         # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
4154         # as we don't want to exclude results that have NULL values for the m2o
4155         src_table, src_field = qualified_field.replace('"','').split('.', 1)
4156         query.join((src_table, dest_model._table, src_field, 'id'), outer=True)
4157         qualify = lambda field: '"%s"."%s"' % (dest_model._table, field)
4158         return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
4159
4160
4161     def _generate_order_by(self, order_spec, query):
4162         """
4163         Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
4164         a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
4165
4166         :raise" except_orm in case order_spec is malformed
4167         """
4168         order_by_clause = self._order
4169         if order_spec:
4170             order_by_elements = []
4171             self._check_qorder(order_spec)
4172             for order_part in order_spec.split(','):
4173                 order_split = order_part.strip().split(' ')
4174                 order_field = order_split[0].strip()
4175                 order_direction = order_split[1].strip() if len(order_split) == 2 else ''
4176                 inner_clause = None
4177                 if order_field == 'id':
4178                     order_by_clause = '"%s"."%s"' % (self._table, order_field)
4179                 elif order_field in self._columns:
4180                     order_column = self._columns[order_field]
4181                     if order_column._classic_read:
4182                         inner_clause = '"%s"."%s"' % (self._table, order_field)
4183                     elif order_column._type == 'many2one':
4184                         inner_clause = self._generate_m2o_order_by(order_field, query)
4185                     else:
4186                         continue # ignore non-readable or "non-joinable" fields
4187                 elif order_field in self._inherit_fields:
4188                     parent_obj = self.pool.get(self._inherit_fields[order_field][0])
4189                     order_column = parent_obj._columns[order_field]
4190                     if order_column._classic_read:
4191                         inner_clause = self._inherits_join_calc(order_field, query)
4192                     elif order_column._type == 'many2one':
4193                         inner_clause = self._generate_m2o_order_by(order_field, query)
4194                     else:
4195                         continue # ignore non-readable or "non-joinable" fields
4196                 if inner_clause:
4197                     if isinstance(inner_clause, list):
4198                         for clause in inner_clause:
4199                             order_by_elements.append("%s %s" % (clause, order_direction))
4200                     else:
4201                         order_by_elements.append("%s %s" % (inner_clause, order_direction))
4202             if order_by_elements:
4203                 order_by_clause = ",".join(order_by_elements)
4204
4205         return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4206
4207     def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
4208         """
4209         Private implementation of search() method, allowing specifying the uid to use for the access right check.
4210         This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4211         by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4212         This is ok at the security level because this method is private and not callable through XML-RPC.
4213
4214         :param access_rights_uid: optional user ID to use when checking access rights
4215                                   (not for ir.rules, this is only for ir.model.access)
4216         """
4217         if context is None:
4218             context = {}
4219         self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
4220
4221         query = self._where_calc(cr, user, args, context=context)
4222         self._apply_ir_rules(cr, user, query, 'read', context=context)
4223         order_by = self._generate_order_by(order, query)
4224         from_clause, where_clause, where_clause_params = query.get_sql()
4225
4226         limit_str = limit and ' limit %d' % limit or ''
4227         offset_str = offset and ' offset %d' % offset or ''
4228         where_str = where_clause and (" WHERE %s" % where_clause) or ''
4229
4230         if count:
4231             cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
4232             res = cr.fetchall()
4233             return res[0][0]
4234         cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
4235         res = cr.fetchall()
4236         return [x[0] for x in res]
4237
4238     # returns the different values ever entered for one field
4239     # this is used, for example, in the client when the user hits enter on
4240     # a char field
4241     def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4242         if not args:
4243             args = []
4244         if field in self._inherit_fields:
4245             return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
4246         else:
4247             return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4248
4249     def copy_data(self, cr, uid, id, default=None, context=None):
4250         """
4251         Copy given record's data with all its fields values
4252
4253         :param cr: database cursor
4254         :param user: current user id
4255         :param id: id of the record to copy
4256         :param default: field values to override in the original values of the copied record
4257         :type default: dictionary
4258         :param context: context arguments, like lang, time zone
4259         :type context: dictionary
4260         :return: dictionary containing all the field values
4261         """
4262
4263         if context is None:
4264             context = {}
4265
4266         # avoid recursion through already copied records in case of circular relationship
4267         seen_map = context.setdefault('__copy_data_seen',{})
4268         if id in seen_map.setdefault(self._name,[]):
4269             return
4270         seen_map[self._name].append(id)
4271
4272         if default is None:
4273             default = {}
4274         if 'state' not in default:
4275             if 'state' in self._defaults:
4276                 if callable(self._defaults['state']):
4277                     default['state'] = self._defaults['state'](self, cr, uid, context)
4278                 else:
4279                     default['state'] = self._defaults['state']
4280
4281         context_wo_lang = context.copy()
4282         if 'lang' in context:
4283             del context_wo_lang['lang']
4284         data = self.read(cr, uid, [id,], context=context_wo_lang)
4285         if data:
4286             data = data[0]
4287         else:
4288             raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
4289
4290         fields = self.fields_get(cr, uid, context=context)
4291         for f in fields:
4292             ftype = fields[f]['type']
4293
4294             if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'):
4295                 del data[f]
4296
4297             if f in default:
4298                 data[f] = default[f]
4299             elif 'function' in fields[f]:
4300                 del data[f]
4301             elif ftype == 'many2one':
4302                 try:
4303                     data[f] = data[f] and data[f][0]
4304                 except:
4305                     pass
4306             elif ftype in ('one2many', 'one2one'):
4307                 res = []
4308                 rel = self.pool.get(fields[f]['relation'])
4309                 if data[f]:
4310                     # duplicate following the order of the ids
4311                     # because we'll rely on it later for copying
4312                     # translations in copy_translation()!
4313                     data[f].sort()
4314                     for rel_id in data[f]:
4315                         # the lines are first duplicated using the wrong (old)
4316                         # parent but then are reassigned to the correct one thanks
4317                         # to the (0, 0, ...)
4318                         d = rel.copy_data(cr, uid, rel_id, context=context)
4319                         if d:
4320                             res.append((0, 0, d))
4321                 data[f] = res
4322             elif ftype == 'many2many':
4323                 data[f] = [(6, 0, data[f])]
4324
4325         del data['id']
4326
4327         # make sure we don't break the current parent_store structure and
4328         # force a clean recompute!
4329         for parent_column in ['parent_left', 'parent_right']:
4330             data.pop(parent_column, None)
4331
4332         for v in self._inherits:
4333             del data[self._inherits[v]]
4334         return data
4335
4336     def copy_translations(self, cr, uid, old_id, new_id, context=None):
4337         if context is None:
4338             context = {}
4339
4340         # avoid recursion through already copied records in case of circular relationship
4341         seen_map = context.setdefault('__copy_translations_seen',{})
4342         if old_id in seen_map.setdefault(self._name,[]):
4343             return
4344         seen_map[self._name].append(old_id)
4345
4346         trans_obj = self.pool.get('ir.translation')
4347         fields = self.fields_get(cr, uid, context=context)
4348
4349         translation_records = []
4350         for field_name, field_def in fields.items():
4351             # we must recursively copy the translations for o2o and o2m
4352             if field_def['type'] in ('one2one', 'one2many'):
4353                 target_obj = self.pool.get(field_def['relation'])
4354                 old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
4355                 # here we rely on the order of the ids to match the translations
4356                 # as foreseen in copy_data()
4357                 old_children = sorted(old_record[field_name])
4358                 new_children = sorted(new_record[field_name])
4359                 for (old_child, new_child) in zip(old_children, new_children):
4360                     target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
4361             # and for translatable fields we keep them for copy
4362             elif field_def.get('translate'):
4363                 trans_name = ''
4364                 if field_name in self._columns:
4365                     trans_name = self._name + "," + field_name
4366                 elif field_name in self._inherit_fields:
4367                     trans_name = self._inherit_fields[field_name][0] + "," + field_name
4368                 if trans_name:
4369                     trans_ids = trans_obj.search(cr, uid, [
4370                             ('name', '=', trans_name),
4371                             ('res_id', '=', old_id)
4372                     ])
4373                     translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
4374
4375         for record in translation_records:
4376             del record['id']
4377             record['res_id'] = new_id
4378             trans_obj.create(cr, uid, record, context=context)
4379
4380
4381     def copy(self, cr, uid, id, default=None, context=None):
4382         """
4383         Duplicate record with given id updating it with default values
4384
4385         :param cr: database cursor
4386         :param uid: current user id
4387         :param id: id of the record to copy
4388         :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
4389         :type default: dictionary
4390         :param context: context arguments, like lang, time zone
4391         :type context: dictionary
4392         :return: True
4393
4394         """
4395         if context is None:
4396             context = {}
4397         context = context.copy()
4398         data = self.copy_data(cr, uid, id, default, context)
4399         new_id = self.create(cr, uid, data, context)
4400         self.copy_translations(cr, uid, id, new_id, context)
4401         return new_id
4402
4403     def exists(self, cr, uid, ids, context=None):
4404         if type(ids) in (int, long):
4405             ids = [ids]
4406         query = 'SELECT count(1) FROM "%s"' % (self._table)
4407         cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
4408         return cr.fetchone()[0] == len(ids)
4409
4410     def check_recursion(self, cr, uid, ids, context=None, parent=None):
4411         warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
4412                         self._name, DeprecationWarning, stacklevel=3)
4413         assert parent is None or parent in self._columns or parent in self._inherit_fields,\
4414                     "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
4415         return self._check_recursion(cr, uid, ids, context, parent)
4416
4417     def _check_recursion(self, cr, uid, ids, context=None, parent=None):
4418         """
4419         Verifies that there is no loop in a hierarchical structure of records,
4420         by following the parent relationship using the **parent** field until a loop
4421         is detected or until a top-level record is found.
4422
4423         :param cr: database cursor
4424         :param uid: current user id
4425         :param ids: list of ids of records to check
4426         :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
4427         :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
4428         """
4429
4430         if not parent:
4431             parent = self._parent_name
4432         ids_parent = ids[:]
4433         query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
4434         while ids_parent:
4435             ids_parent2 = []
4436             for i in range(0, len(ids), cr.IN_MAX):
4437                 sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
4438                 cr.execute(query, (tuple(sub_ids_parent),))
4439                 ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
4440             ids_parent = ids_parent2
4441             for i in ids_parent:
4442                 if i in ids:
4443                     return False
4444         return True
4445
4446     def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
4447         """Find out the XML ID(s) of any database record.
4448
4449         **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
4450
4451         :return: map of ids to the list of their fully qualified XML IDs
4452                  (empty list when there's none).
4453         """
4454         model_data_obj = self.pool.get('ir.model.data')
4455         data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
4456         data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
4457         result = {}
4458         for id in ids:
4459             # can't use dict.fromkeys() as the list would be shared!
4460             result[id] = []
4461         for record in data_results:
4462             result[record['res_id']].append('%(module)s.%(name)s' % record)
4463         return result
4464
4465     def get_xml_id(self, cr, uid, ids, *args, **kwargs):
4466         """Find out the XML ID of any database record, if there
4467         is one. This method works as a possible implementation
4468         for a function field, to be able to add it to any
4469         model object easily, referencing it as ``osv.osv.get_xml_id``.
4470
4471         When multiple XML IDs exist for a record, only one
4472         of them is returned (randomly).
4473
4474         **Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
4475
4476         :return: map of ids to their fully qualified XML ID,
4477                  defaulting to an empty string when there's none
4478                  (to be usable as a function field).
4479         """
4480         results = self._get_xml_ids(cr, uid, ids)
4481         for k, v in results.items():
4482             if results[k]:
4483                 results[k] = v[0]
4484             else:
4485                 results[k] = ''
4486         return results
4487
4488 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
4489