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