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