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