~openerp-usertesting/openobject-server/trunk-muilti_address-rmu

1864 by pap(openerp)
Changed encoding to coding ref: PEP: 0263
1
# -*- coding: utf-8 -*-
1 by pinky
New trunk
2
##############################################################################
1869.1.14 by nch at tinyerp
read_group method for group by
3
#
1242 by hda at tinyerp
bugfix for active=false search
4
#    OpenERP, Open Source Management Solution
1903 by Harry (Open ERP)
[FIX] orm, osv: change copyright with AGPL
5
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
1230 by Christophe Simonis
passing in GPL-3
6
#
7
#    This program is free software: you can redistribute it and/or modify
1903 by Harry (Open ERP)
[FIX] orm, osv: change copyright with AGPL
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.
1230 by Christophe Simonis
passing in GPL-3
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
1903 by Harry (Open ERP)
[FIX] orm, osv: change copyright with AGPL
15
#    GNU Affero General Public License for more details.
1230 by Christophe Simonis
passing in GPL-3
16
#
1903 by Harry (Open ERP)
[FIX] orm, osv: change copyright with AGPL
17
#    You should have received a copy of the GNU Affero General Public License
1869.1.14 by nch at tinyerp
read_group method for group by
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
1230 by Christophe Simonis
passing in GPL-3
19
#
20
##############################################################################
1 by pinky
New trunk
21
3471.2.7 by P. Christeas
API docs: settings file and titles at modules
22
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
23
"""
24
  Object relational mapping to database (postgresql) module
25
     * Hierarchical structure
26
     * Constraints consistency, validations
27
     * Object meta Data depends on its status
28
     * Optimised processing by complex query (multiple actions at once)
29
     * Default fields value
30
     * Permissions optimisation
31
     * Persistant object: DB postgresql
32
     * Datas conversions
33
     * Multi-level caching system
34
     * 2 different inheritancies
35
     * Fields:
36
          - classicals (varchar, integer, boolean, ...)
37
          - relations (one2many, many2one, many2many)
38
          - functions
3549.3.1 by tfr(OpenERP)
[FIX]:import boolean fields
39
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
40
"""
41
4486 by Vo Minh Thu
[MERGE] babel: use babel for locale-aware read_group date formatting.
42
import babel.dates
2013 by Xavier Morel
[imp] fix imports
43
import calendar
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
44
import collections
2013 by Xavier Morel
[imp] fix imports
45
import copy
1845 by HDA(OpenERP)
[Merged]
46
import datetime
3594.1.11 by Xavier Morel
[IMP] fields_view_get: listcomps are still awesome
47
import itertools
2096 by Xavier Morel
[imp] migrate datas warning in browse_record to logging
48
import logging
2288 by Xavier Morel
[FIX] imports in orm, remove unneeded imports and pointlessly (and incorrectly) defensive garbage
49
import operator
2013 by Xavier Morel
[imp] fix imports
50
import pickle
51
import re
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
52
import simplejson
2013 by Xavier Morel
[imp] fix imports
53
import time
4466.1.1 by Christophe Simonis
[IMP] orm: be more vebose when accessing an invalid field of browse_record objects
54
import traceback
2013 by Xavier Morel
[imp] fix imports
55
import types
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
56
57
import psycopg2
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
58
from lxml import etree
3790.1.1 by Xavier Morel
[IMP] Use babel's locale-aware date formatting when formatting dates in read_group titles
59
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
60
import fields
61
import openerp
62
import openerp.tools as tools
3346.1.1 by Vo Minh Thu
[IMP] openerp python module.
63
from openerp.tools.config import config
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
64
from openerp.tools.misc import CountingStream
4477.1.2 by Quentin (OpenERP)
[IMP] use of literal_eval from ast instead of const_eval from tools, as per Olivier recommendation
65
from openerp.tools.safe_eval import safe_eval as eval
3346.1.1 by Vo Minh Thu
[IMP] openerp python module.
66
from openerp.tools.translate import _
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
67
from openerp import SUPERUSER_ID
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
68
from query import Query
773 by Fabien Pinckaers
Modifs
69
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
70
_logger = logging.getLogger(__name__)
3976.1.27 by Antony Lesuisse
[FIX] review according to xmo :)
71
_schema = logging.getLogger(__name__ + '.schema')
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
72
3167 by Olivier Dony
[FIX] osv,tools: break some circular dependencies
73
# List of etree._Element subclasses that we choose to ignore when parsing XML.
3346.1.1 by Vo Minh Thu
[IMP] openerp python module.
74
from openerp.tools import SKIPPED_ELEMENT_TYPES
3167 by Olivier Dony
[FIX] osv,tools: break some circular dependencies
75
1844.4.93 by Jay(Open ERP)
[FIX] Allowed SQL reserved keywords to be used in _order,group by ,etc.[Make sure , your _order should look like _order='to' instead of to]
76
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
3417.6.13 by Vo Minh Thu
[IMP] orm: check for object _name validity.
77
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
78
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
79
def transfer_field_to_modifiers(field, modifiers):
3498.1.1 by niv-openerp
[imp] Improved modifiers algorithm to also take the "states" attribute into account.
80
    default_values = {}
81
    state_exceptions = {}
3498.1.3 by Antony Lesuisse
[FIX] apply some merge review feeback
82
    for attr in ('invisible', 'readonly', 'required'):
83
        state_exceptions[attr] = []
84
        default_values[attr] = bool(field.get(attr))
85
    for state, modifs in (field.get("states",{})).items():
3498.1.1 by niv-openerp
[imp] Improved modifiers algorithm to also take the "states" attribute into account.
86
        for modif in modifs:
3498.1.3 by Antony Lesuisse
[FIX] apply some merge review feeback
87
            if default_values[modif[0]] != modif[1]:
3498.1.1 by niv-openerp
[imp] Improved modifiers algorithm to also take the "states" attribute into account.
88
                state_exceptions[modif[0]].append(state)
3498.1.3 by Antony Lesuisse
[FIX] apply some merge review feeback
89
3498.1.1 by niv-openerp
[imp] Improved modifiers algorithm to also take the "states" attribute into account.
90
    for attr, default_value in default_values.items():
3498.1.3 by Antony Lesuisse
[FIX] apply some merge review feeback
91
        if state_exceptions[attr]:
92
            modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])]
93
        else:
94
            modifiers[attr] = default_value
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
95
96
97
# Don't deal with groups, it is done by check_group().
98
# Need the context to evaluate the invisible attribute on tree views.
99
# For non-tree views, the context shouldn't be given.
100
def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False):
101
    if node.get('attrs'):
102
        modifiers.update(eval(node.get('attrs')))
103
104
    if node.get('states'):
105
        if 'invisible' in modifiers and isinstance(modifiers['invisible'], list):
3974.2.2 by Florent Xicluna
[REF] fix weird indentation, not multiple of four.
106
            # TODO combine with AND or OR, use implicit AND for now.
107
            modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
108
        else:
3974.2.2 by Florent Xicluna
[REF] fix weird indentation, not multiple of four.
109
            modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
110
111
    for a in ('invisible', 'readonly', 'required'):
112
        if node.get(a):
113
            v = bool(eval(node.get(a), {'context': context or {}}))
114
            if in_tree_view and a == 'invisible':
115
                # Invisible in a tree view has a specific meaning, make it a
116
                # new key in the modifiers attribute.
117
                modifiers['tree_invisible'] = v
118
            elif v or (a not in modifiers or not isinstance(modifiers[a], list)):
119
                # Don't set the attribute to False if a dynamic value was
120
                # provided (i.e. a domain from attrs or states).
121
                modifiers[a] = v
122
123
124
def simplify_modifiers(modifiers):
125
    for a in ('invisible', 'readonly', 'required'):
126
        if a in modifiers and not modifiers[a]:
127
            del modifiers[a]
128
129
130
def transfer_modifiers_to_node(modifiers, node):
131
    if modifiers:
132
        simplify_modifiers(modifiers)
133
        node.set('modifiers', simplejson.dumps(modifiers))
134
3738 by Xavier Morel
[ADD] wrapper to the modifiers setup process to simplify its call by third parties
135
def setup_modifiers(node, field=None, context=None, in_tree_view=False):
136
    """ Processes node attributes and field descriptors to generate
137
    the ``modifiers`` node attribute and set it on the provided node.
138
139
    Alters its first argument in-place.
140
141
    :param node: ``field`` node from an OpenERP view
142
    :type node: lxml.etree._Element
143
    :param dict field: field descriptor corresponding to the provided node
144
    :param dict context: execution context used to evaluate node attributes
145
    :param bool in_tree_view: triggers the ``tree_invisible`` code
146
                              path (separate from ``invisible``): in
147
                              tree view there are two levels of
148
                              invisibility, cell content (a column is
149
                              present but the cell itself is not
150
                              displayed) with ``invisible`` and column
151
                              invisibility (the whole column is
152
                              hidden) with ``tree_invisible``.
153
    :returns: nothing
154
    """
155
    modifiers = {}
156
    if field is not None:
157
        transfer_field_to_modifiers(field, modifiers)
158
    transfer_node_to_modifiers(
159
        node, modifiers, context=context, in_tree_view=in_tree_view)
160
    transfer_modifiers_to_node(modifiers, node)
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
161
162
def test_modifiers(what, expected):
163
    modifiers = {}
164
    if isinstance(what, basestring):
165
        node = etree.fromstring(what)
166
        transfer_node_to_modifiers(node, modifiers)
167
        simplify_modifiers(modifiers)
168
        json = simplejson.dumps(modifiers)
169
        assert json == expected, "%s != %s" % (json, expected)
170
    elif isinstance(what, dict):
171
        transfer_field_to_modifiers(what, modifiers)
172
        simplify_modifiers(modifiers)
173
        json = simplejson.dumps(modifiers)
174
        assert json == expected, "%s != %s" % (json, expected)
175
176
177
# To use this test:
178
# import openerp
179
# openerp.osv.orm.modifiers_tests()
180
def modifiers_tests():
181
    test_modifiers('<field name="a"/>', '{}')
182
    test_modifiers('<field name="a" invisible="1"/>', '{"invisible": true}')
183
    test_modifiers('<field name="a" readonly="1"/>', '{"readonly": true}')
184
    test_modifiers('<field name="a" required="1"/>', '{"required": true}')
185
    test_modifiers('<field name="a" invisible="0"/>', '{}')
186
    test_modifiers('<field name="a" readonly="0"/>', '{}')
187
    test_modifiers('<field name="a" required="0"/>', '{}')
188
    test_modifiers('<field name="a" invisible="1" required="1"/>', '{"invisible": true, "required": true}') # TODO order is not guaranteed
189
    test_modifiers('<field name="a" invisible="1" required="0"/>', '{"invisible": true}')
190
    test_modifiers('<field name="a" invisible="0" required="1"/>', '{"required": true}')
191
    test_modifiers("""<field name="a" attrs="{'invisible': [('b', '=', 'c')]}"/>""", '{"invisible": [["b", "=", "c"]]}')
192
193
    # The dictionary is supposed to be the result of fields_get().
194
    test_modifiers({}, '{}')
195
    test_modifiers({"invisible": True}, '{"invisible": true}')
196
    test_modifiers({"invisible": False}, '{}')
3549.3.1 by tfr(OpenERP)
[FIX]:import boolean fields
197
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
198
3417.6.13 by Vo Minh Thu
[IMP] orm: check for object _name validity.
199
def check_object_name(name):
200
    """ Check if the given name is a valid openerp object name.
201
202
        The _name attribute in osv and osv_memory object is subject to
203
        some restrictions. This function returns True or False whether
204
        the given name is allowed or not.
205
206
        TODO: this is an approximation. The goal in this approximation
207
        is to disallow uppercase characters (in some places, we quote
208
        table/column names and in other not, which leads to this kind
209
        of errors:
210
211
            psycopg2.ProgrammingError: relation "xxx" does not exist).
212
213
        The same restriction should apply to both osv and osv_memory
214
        objects for consistency.
215
216
    """
217
    if regex_object_name.match(name) is None:
218
        return False
219
    return True
220
221
def raise_on_invalid_object_name(name):
222
    if not check_object_name(name):
223
        msg = "The _name attribute %s is not valid." % name
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
224
        _logger.error(msg)
3417.6.13 by Vo Minh Thu
[IMP] orm: check for object _name validity.
225
        raise except_orm('ValueError', msg)
772 by Fabien Pinckaers
Bugfix: sql injection
226
2156 by Olivier Dony
[REF] Refactored postgres' confdeltypes as a constant
227
POSTGRES_CONFDELTYPES = {
228
    'RESTRICT': 'r',
229
    'NO ACTION': 'a',
230
    'CASCADE': 'c',
231
    'SET NULL': 'n',
232
    'SET DEFAULT': 'd',
233
}
234
1 by pinky
New trunk
235
def intersect(la, lb):
922 by Christophe Simonis
convert tabs to 4 spaces
236
    return filter(lambda x: x in lb, la)
1 by pinky
New trunk
237
3641 by Xavier Morel
[IMP] use same fieldpath-splitting function in data_import and data_export, document it a bit
238
def fix_import_export_id_paths(fieldname):
239
    """
240
    Fixes the id fields in import and exports, and splits field paths
241
    on '/'.
242
243
    :param str fieldname: name of the field to import/export
244
    :return: split field name
245
    :rtype: list of str
246
    """
247
    fixed_db_id = re.sub(r'([^/])\.id', r'\1/.id', fieldname)
248
    fixed_external_id = re.sub(r'([^/]):id', r'\1/id', fixed_db_id)
249
    return fixed_external_id.split('/')
250
1 by pinky
New trunk
251
class except_orm(Exception):
922 by Christophe Simonis
convert tabs to 4 spaces
252
    def __init__(self, name, value):
253
        self.name = name
254
        self.value = value
255
        self.args = (name, value)
1 by pinky
New trunk
256
1786.1.1 by Christophe Simonis
[FIX] browse_record: raise a better exception when the id doesn't exists
257
class BrowseRecordError(Exception):
258
    pass
957 by Olivier Laurent
pep8
259
1 by pinky
New trunk
260
class browse_null(object):
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
261
    """ Readonly python database object browser
262
    """
391 by ced
kernel: don't modify the args of the call
263
922 by Christophe Simonis
convert tabs to 4 spaces
264
    def __init__(self):
957 by Olivier Laurent
pep8
265
        self.id = False
922 by Christophe Simonis
convert tabs to 4 spaces
266
267
    def __getitem__(self, name):
1119.1.60 by P. Christeas
ORM browse: return None when sth is wrong, not False
268
        return None
922 by Christophe Simonis
convert tabs to 4 spaces
269
1200 by Christophe Simonis
browse_null objects doesn't fail anymore when accessing its attributes
270
    def __getattr__(self, name):
1119.1.60 by P. Christeas
ORM browse: return None when sth is wrong, not False
271
        return None  # XXX: return self ?
1200 by Christophe Simonis
browse_null objects doesn't fail anymore when accessing its attributes
272
922 by Christophe Simonis
convert tabs to 4 spaces
273
    def __int__(self):
274
        return False
275
276
    def __str__(self):
277
        return ''
278
279
    def __nonzero__(self):
280
        return False
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
281
1521 by Jay (Open ERP)
Bugfix on report :Added unicode method on browse_null
282
    def __unicode__(self):
283
        return u''
1 by pinky
New trunk
284
957 by Olivier Laurent
pep8
285
1 by pinky
New trunk
286
#
287
# TODO: execute an object method on browse_record_list
288
#
289
class browse_record_list(list):
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
290
    """ Collection of browse objects
3549.3.1 by tfr(OpenERP)
[FIX]:import boolean fields
291
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
292
        Such an instance will be returned when doing a ``browse([ids..])``
293
        and will be iterable, yielding browse() objects
294
    """
392 by ced
kernel: don't use mutable as default value in function defintion.
295
922 by Christophe Simonis
convert tabs to 4 spaces
296
    def __init__(self, lst, context=None):
297
        if not context:
298
            context = {}
299
        super(browse_record_list, self).__init__(lst)
300
        self.context = context
1 by pinky
New trunk
301
456 by ced
kernel: use type for browse record test instead os instance
302
1 by pinky
New trunk
303
class browse_record(object):
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
304
    """ An object that behaves like a row of an object's table.
305
        It has attributes after the columns of the corresponding object.
3549.3.1 by tfr(OpenERP)
[FIX]:import boolean fields
306
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
307
        Examples::
3549.3.1 by tfr(OpenERP)
[FIX]:import boolean fields
308
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
309
            uobj = pool.get('res.users')
310
            user_rec = uobj.browse(cr, uid, 104)
311
            name = user_rec.name
312
    """
2012 by Xavier Morel
[fix] exception thrown by browse_record.__getitem__ when field not found
313
3980 by Xavier Morel
[IMP] move browse_record to logging, __init__ docstring to sphinx info fields
314
    def __init__(self, cr, uid, id, table, cache, context=None,
315
                 list_class=browse_record_list, fields_process=None):
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
316
        """
3980 by Xavier Morel
[IMP] move browse_record to logging, __init__ docstring to sphinx info fields
317
        :param table: the browsed object (inherited from orm)
318
        :param dict cache: a dictionary of model->field->data to be shared
319
                           across browse objects, thus reducing the SQL
320
                           read()s. It can speed up things a lot, but also be
321
                           disastrous if not discarded after write()/unlink()
322
                           operations
323
        :param dict context: dictionary with an optional context
3471.2.1 by P. Christeas
orm, tools, addons: Doc strings improvements
324
        """
2537.1.4 by Numerigraphe - Lionel Sausin
[REF] stricter checking of defauts in fields & orm __init__
325
        if fields_process is None:
2537.1.6 by Numerigraphe - Lionel Sausin
[FIX] typo in field name
326
            fields_process = {}
2537.1.4 by Numerigraphe - Lionel Sausin
[REF] stricter checking of defauts in fields & orm __init__
327
        if context is None:
328
            context = {}
3980 by Xavier Morel
[IMP] move browse_record to logging, __init__ docstring to sphinx info fields
329
        self._list_class = list_class
922 by Christophe Simonis
convert tabs to 4 spaces
330
        self._cr = cr
331
        self._uid = uid
332
        self._id = id
3427.2.35 by Olivier Dony
[IMP] browse_record._table -> browse_record._model
333
        self._table = table # deprecated, use _model!
334
        self._model = table
922 by Christophe Simonis
convert tabs to 4 spaces
335
        self._table_name = self._table._name
3976.1.21 by Antony Lesuisse
[MERGE] trunk
336
        self.__logger = logging.getLogger('openerp.osv.orm.browse_record.' + self._table_name)
2537.1.4 by Numerigraphe - Lionel Sausin
[REF] stricter checking of defauts in fields & orm __init__
337
        self._context = context
338
        self._fields_process = fields_process
922 by Christophe Simonis
convert tabs to 4 spaces
339
340
        cache.setdefault(table._name, {})
341
        self._data = cache[table._name]
1272.1.2 by Fabien Pinckaers
improve
342
3895 by Fabien Pinckaers
[IMP] allow non integer for ID's
343
#        if not (id and isinstance(id, (int, long,))):
344
#            raise BrowseRecordError(_('Wrong ID for the browse record, got %r, expected an integer.') % (id,))
1789.1.3 by Jay(Open ERP)
[IMP] Temporarily commented exception for browse record that broke reports from wizard
345
#        if not table.exists(cr, uid, id, context):
346
#            raise BrowseRecordError(_('Object %s does not exists') % (self,))
1786.1.1 by Christophe Simonis
[FIX] browse_record: raise a better exception when the id doesn't exists
347
1460 by Christophe Simonis
[IMP] "not x in l" -> "x not in l"
348
        if id not in self._data:
957 by Olivier Laurent
pep8
349
            self._data[id] = {'id': id}
1272.1.2 by Fabien Pinckaers
improve
350
922 by Christophe Simonis
convert tabs to 4 spaces
351
        self._cache = cache
352
353
    def __getitem__(self, name):
354
        if name == 'id':
355
            return self._id
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
356
1460 by Christophe Simonis
[IMP] "not x in l" -> "x not in l"
357
        if name not in self._data[self._id]:
922 by Christophe Simonis
convert tabs to 4 spaces
358
            # build the list of fields we will fetch
359
360
            # fetch the definition of the field which was asked for
361
            if name in self._table._columns:
362
                col = self._table._columns[name]
363
            elif name in self._table._inherit_fields:
364
                col = self._table._inherit_fields[name][2]
1119.1.18 by P. Christeas
Behave better at exceptions (l10n, __getitem__() )
365
            elif hasattr(self._table, str(name)):
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
366
                attr = getattr(self._table, name)
367
                if isinstance(attr, (types.MethodType, types.LambdaType, types.FunctionType)):
3353.1.43 by Olivier Dony
[IMP] browse_record: getattr method execution propagates context
368
                    def function_proxy(*args, **kwargs):
3353.1.45 by Olivier Dony
[FIX] orm: avoid passing undefined context in browse_record getattr()
369
                        if 'context' not in kwargs and self._context:
3353.1.43 by Olivier Dony
[IMP] browse_record: getattr method execution propagates context
370
                            kwargs.update(context=self._context)
371
                        return attr(self._cr, self._uid, [self._id], *args, **kwargs)
372
                    return function_proxy
922 by Christophe Simonis
convert tabs to 4 spaces
373
                else:
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
374
                    return attr
922 by Christophe Simonis
convert tabs to 4 spaces
375
            else:
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
376
                error_msg = "Field '%s' does not exist in object '%s'" % (name, self)
3976.1.29 by Vo Minh Thu
[IMP] logging: _logger.warn() replaced by _logger.warning().
377
                self.__logger.warning(error_msg)
4466.1.1 by Christophe Simonis
[IMP] orm: be more vebose when accessing an invalid field of browse_record objects
378
                if self.__logger.isEnabledFor(logging.DEBUG):
379
                    self.__logger.debug(''.join(traceback.format_stack()))
3743 by Olivier Dony
[FIX] orm.browse_record: avoid printing unrelated traceback upon KeyError
380
                raise KeyError(error_msg)
922 by Christophe Simonis
convert tabs to 4 spaces
381
382
            # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
1828 by Fabien Pinckaers
[IMP] Speed impprovement: 2x faster for flow: sale -> invoice -> payment
383
            if col._prefetch:
922 by Christophe Simonis
convert tabs to 4 spaces
384
                # gen the list of "local" (ie not inherited) fields which are classic or many2one
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
385
                fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
922 by Christophe Simonis
convert tabs to 4 spaces
386
                # gen the list of inherited fields
387
                inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
388
                # complete the field list with the inherited fields which are classic or many2one
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
389
                fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
922 by Christophe Simonis
convert tabs to 4 spaces
390
            # otherwise we fetch only that field
391
            else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
392
                fields_to_fetch = [(name, col)]
1460 by Christophe Simonis
[IMP] "not x in l" -> "x not in l"
393
            ids = filter(lambda id: name not in self._data[id], self._data.keys())
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
394
            # read the results
395
            field_names = map(lambda x: x[0], fields_to_fetch)
396
            field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
3296 by Fabien Pinckaers
[IMP] comment for later use
397
398
            # TODO: improve this, very slow for reports
922 by Christophe Simonis
convert tabs to 4 spaces
399
            if self._fields_process:
1766.1.1 by Naresh Choksy
added report engine
400
                lang = self._context.get('lang', 'en_US') or 'en_US'
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
401
                lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
1849 by HDA(OpenERP)
rename html2html to Mako2html Merging improvement in orm
402
                if not lang_obj_ids:
403
                    raise Exception(_('Language with code "%s" is not defined in your system !\nDefine it through the Administration menu.') % (lang,))
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
404
                lang_obj = self.pool.get('res.lang').browse(self._cr, self._uid, lang_obj_ids[0])
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
405
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
406
                for field_name, field_column in fields_to_fetch:
407
                    if field_column._type in self._fields_process:
408
                        for result_line in field_values:
409
                            result_line[field_name] = self._fields_process[field_column._type](result_line[field_name])
410
                            if result_line[field_name]:
411
                                result_line[field_name].set_value(self._cr, self._uid, result_line[field_name], self, field_column, lang_obj)
412
413
            if not field_values:
2012 by Xavier Morel
[fix] exception thrown by browse_record.__getitem__ when field not found
414
                # Where did those ids come from? Perhaps old entries in ir_model_dat?
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
415
                _logger.warning("No field_values found for ids %s in %s", ids, self)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
416
                raise KeyError('Field %s not found in %s'%(name, self))
922 by Christophe Simonis
convert tabs to 4 spaces
417
            # create browse records for 'remote' objects
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
418
            for result_line in field_values:
1844.4.102 by Jay(Open ERP)
[FIX] Browse with _inherits issue fixed if more than one M2O to parent objct exists
419
                new_data = {}
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
420
                for field_name, field_column in fields_to_fetch:
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
421
                    if field_column._type == 'many2one':
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
422
                        if result_line[field_name]:
423
                            obj = self._table.pool.get(field_column._obj)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
424
                            if isinstance(result_line[field_name], (list, tuple)):
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
425
                                value = result_line[field_name][0]
922 by Christophe Simonis
convert tabs to 4 spaces
426
                            else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
427
                                value = result_line[field_name]
428
                            if value:
1844.4.91 by Christophe Simonis
[FIX] orm: bug when browsing _inherits object
429
                                # FIXME: this happen when a _inherits object
430
                                #        overwrite a field of it parent. Need
431
                                #        testing to be sure we got the right
432
                                #        object and not the parent one.
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
433
                                if not isinstance(value, browse_record):
3310.1.33 by Olivier Dony
[FIX] orm.browse_record: fields prefetching should ignore m2o pointing to models not loaded yet
434
                                    if obj is None:
435
                                        # In some cases the target model is not available yet, so we must ignore it,
436
                                        # which is safe in most cases, this value will just be loaded later when needed.
437
                                        # This situation can be caused by custom fields that connect objects with m2o without
438
                                        # respecting module dependencies, causing relationships to be connected to soon when
439
                                        # the target is not loaded yet.
440
                                        continue
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
441
                                    new_data[field_name] = browse_record(self._cr,
442
                                        self._uid, value, obj, self._cache,
1844.4.91 by Christophe Simonis
[FIX] orm: bug when browsing _inherits object
443
                                        context=self._context,
444
                                        list_class=self._list_class,
445
                                        fields_process=self._fields_process)
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
446
                                else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
447
                                    new_data[field_name] = value
922 by Christophe Simonis
convert tabs to 4 spaces
448
                            else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
449
                                new_data[field_name] = browse_null()
922 by Christophe Simonis
convert tabs to 4 spaces
450
                        else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
451
                            new_data[field_name] = browse_null()
452
                    elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
453
                        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)
4682.2.23 by Raphael Collet
[FIX] orm: replace incorrect usage of 'in' by '=='
454
                    elif field_column._type == 'reference':
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
455
                        if result_line[field_name]:
456
                            if isinstance(result_line[field_name], browse_record):
457
                                new_data[field_name] = result_line[field_name]
2144 by HDA(OpenERP)
FIX reference field support browse in property field
458
                            else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
459
                                ref_obj, ref_id = result_line[field_name].split(',')
2144 by HDA(OpenERP)
FIX reference field support browse in property field
460
                                ref_id = long(ref_id)
3129.1.2 by Olivier Dony
[FIX] fields.reference: don't accept half-defined references, and avoid crashing on previous bad ones
461
                                if ref_id:
462
                                    obj = self._table.pool.get(ref_obj)
463
                                    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)
464
                                else:
465
                                    new_data[field_name] = browse_null()
2111.1.16 by Rga(Open ERP)
[IMP] browse() on reference fields
466
                        else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
467
                            new_data[field_name] = browse_null()
1844.4.102 by Jay(Open ERP)
[FIX] Browse with _inherits issue fixed if more than one M2O to parent objct exists
468
                    else:
2631 by Olivier Dony
[REF] orm.browse_record: renamed variables to improve readability of __getitem__ + minor cleanup
469
                        new_data[field_name] = result_line[field_name]
470
                self._data[result_line['id']].update(new_data)
2238.1.5 by Anup(OpenERP)
[FIX] Copy() of ir_model_fields corected
471
1884.1.4 by Harry (Open ERP)
[FIX] replaced <TAB> with four white space.
472
        if not name in self._data[self._id]:
3310.1.33 by Olivier Dony
[FIX] orm.browse_record: fields prefetching should ignore m2o pointing to models not loaded yet
473
            # How did this happen? Could be a missing model due to custom fields used too soon, see above.
3980 by Xavier Morel
[IMP] move browse_record to logging, __init__ docstring to sphinx info fields
474
            self.__logger.error("Fields to fetch: %s, Field values: %s", field_names, field_values)
475
            self.__logger.error("Cached: %s, Table: %s", self._data[self._id], self._table)
2012 by Xavier Morel
[fix] exception thrown by browse_record.__getitem__ when field not found
476
            raise KeyError(_('Unknown attribute %s in %s ') % (name, self))
922 by Christophe Simonis
convert tabs to 4 spaces
477
        return self._data[self._id][name]
478
479
    def __getattr__(self, name):
2012 by Xavier Morel
[fix] exception thrown by browse_record.__getitem__ when field not found
480
        try:
481
            return self[name]
482
        except KeyError, e:
4815.1.9 by Vo Minh Thu
[REF] orm:
483
            import sys
484
            exc_info = sys.exc_info()
4815.1.17 by Vo Minh Thu
[FIX] ir_model_data: create the _inherits parent external ID prior to the child.
485
            raise AttributeError, "Got %r while trying to get attribute %s on a %s record." % (e, name, self._table._name), exc_info[2]
922 by Christophe Simonis
convert tabs to 4 spaces
486
487
    def __contains__(self, name):
488
        return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
489
3724 by Xavier Morel
[IMP] raise error when iterating over browse_record
490
    def __iter__(self):
491
        raise NotImplementedError("Iteration is not allowed on %s" % self)
492
922 by Christophe Simonis
convert tabs to 4 spaces
493
    def __hasattr__(self, name):
494
        return name in self
495
496
    def __int__(self):
497
        return self._id
498
499
    def __str__(self):
500
        return "browse_record(%s, %d)" % (self._table_name, self._id)
501
502
    def __eq__(self, other):
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
503
        if not isinstance(other, browse_record):
504
            return False
922 by Christophe Simonis
convert tabs to 4 spaces
505
        return (self._table_name, self._id) == (other._table_name, other._id)
506
507
    def __ne__(self, other):
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
508
        if not isinstance(other, browse_record):
509
            return True
922 by Christophe Simonis
convert tabs to 4 spaces
510
        return (self._table_name, self._id) != (other._table_name, other._id)
511
512
    # we need to define __unicode__ even though we've already defined __str__
513
    # because we have overridden __getattr__
514
    def __unicode__(self):
515
        return unicode(str(self))
516
517
    def __hash__(self):
518
        return hash((self._table_name, self._id))
519
520
    __repr__ = __str__
1 by pinky
New trunk
521
3353.1.14 by Olivier Dony
[IMP] browse_record: add refresh() method for discarding the cache
522
    def refresh(self):
523
        """Force refreshing this browse_record's data and all the data of the
524
           records that belong to the same cache, by emptying the cache completely,
525
           preserving only the record identifiers (for prefetching optimizations).
526
        """
527
        for model, model_cache in self._cache.iteritems():
528
            # only preserve the ids of the records that were in the cache
529
            cached_ids = dict([(i, {'id': i}) for i in model_cache.keys()])
530
            self._cache[model].clear()
531
            self._cache[model].update(cached_ids)
1 by pinky
New trunk
532
3579.1.2 by Xavier Morel
[IMP] extract VARCHAR typing, if no size is provided (or the size is 0) don't put a limit on the varchar
533
def pg_varchar(size=0):
534
    """ Returns the VARCHAR declaration for the provided size:
535
3579.1.15 by Vo Minh Thu
[FIX] orm: allow negative varchar size (meaning no limit) as used in some addons.
536
    * If no size (or an empty or negative size is provided) return an
537
      'infinite' VARCHAR
3579.1.2 by Xavier Morel
[IMP] extract VARCHAR typing, if no size is provided (or the size is 0) don't put a limit on the varchar
538
    * Otherwise return a VARCHAR(n)
539
540
    :type int size: varchar size, optional
541
    :rtype: str
542
    """
543
    if size:
3579.1.11 by Xavier Morel
[IMP] add a pair of assertions on the parameter to VARCHAR, just in case
544
        if not isinstance(size, int):
545
            raise TypeError("VARCHAR parameter should be an int, got %s"
546
                            % type(size))
3579.1.15 by Vo Minh Thu
[FIX] orm: allow negative varchar size (meaning no limit) as used in some addons.
547
        if size > 0:
548
            return 'VARCHAR(%d)' % size
3579.1.2 by Xavier Morel
[IMP] extract VARCHAR typing, if no size is provided (or the size is 0) don't put a limit on the varchar
549
    return 'VARCHAR'
1 by pinky
New trunk
550
3579.1.6 by Xavier Morel
[IMP] lift mapping from field types to PGTYPES outside get_pg_type
551
FIELDS_TO_PGTYPES = {
552
    fields.boolean: 'bool',
553
    fields.integer: 'int4',
554
    fields.text: 'text',
4322.1.1 by niv-openerp
Added html field type
555
    fields.html: 'text',
3579.1.6 by Xavier Morel
[IMP] lift mapping from field types to PGTYPES outside get_pg_type
556
    fields.date: 'date',
557
    fields.datetime: 'timestamp',
558
    fields.binary: 'bytea',
559
    fields.many2one: 'int4',
3808.1.1 by Olivier Dony
[MERGE] Implementation of sparse field (to review), by Sebastien Beau, Akretion
560
    fields.serialized: 'text',
3579.1.6 by Xavier Morel
[IMP] lift mapping from field types to PGTYPES outside get_pg_type
561
}
3579.1.13 by Vo Minh Thu
[MERGE] merged trunk.
562
3579.1.3 by Xavier Morel
[IMP] have function fields delegate their pg_type discovery to get_pg_type
563
def get_pg_type(f, type_override=None):
564
    """
565
    :param fields._column f: field to get a Postgres type for
566
    :param type type_override: use the provided type for dispatching instead of the field's own type
567
    :returns: (postgres_identification_type, postgres_type_specification)
568
    :rtype: (str, str)
569
    """
570
    field_type = type_override or type(f)
922 by Christophe Simonis
convert tabs to 4 spaces
571
3579.1.6 by Xavier Morel
[IMP] lift mapping from field types to PGTYPES outside get_pg_type
572
    if field_type in FIELDS_TO_PGTYPES:
3579.1.12 by Vo Minh Thu
[IMP] orm: get_pg_type() does not use early return.
573
        pg_type =  (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type])
3579.1.9 by Xavier Morel
[FIX] if types can be overridden, type-checking against the overriden type instead of the non-overridden instance might be a good idea as well
574
    elif issubclass(field_type, fields.float):
922 by Christophe Simonis
convert tabs to 4 spaces
575
        if f.digits:
3579.1.12 by Vo Minh Thu
[IMP] orm: get_pg_type() does not use early return.
576
            pg_type = ('numeric', 'NUMERIC')
577
        else:
578
            pg_type = ('float8', 'DOUBLE PRECISION')
3579.1.9 by Xavier Morel
[FIX] if types can be overridden, type-checking against the overriden type instead of the non-overridden instance might be a good idea as well
579
    elif issubclass(field_type, (fields.char, fields.reference)):
3579.1.12 by Vo Minh Thu
[IMP] orm: get_pg_type() does not use early return.
580
        pg_type = ('varchar', pg_varchar(f.size))
3579.1.9 by Xavier Morel
[FIX] if types can be overridden, type-checking against the overriden type instead of the non-overridden instance might be a good idea as well
581
    elif issubclass(field_type, fields.selection):
3579.1.10 by Xavier Morel
[FIX] reimplement quality selection field API
582
        if (isinstance(f.selection, list) and isinstance(f.selection[0][0], int))\
583
                or getattr(f, 'size', None) == -1:
3579.1.12 by Vo Minh Thu
[IMP] orm: get_pg_type() does not use early return.
584
            pg_type = ('int4', 'INTEGER')
585
        else:
586
            pg_type = ('varchar', pg_varchar(getattr(f, 'size', None)))
3579.1.9 by Xavier Morel
[FIX] if types can be overridden, type-checking against the overriden type instead of the non-overridden instance might be a good idea as well
587
    elif issubclass(field_type, fields.function):
3579.1.8 by Xavier Morel
[FIX] typo, might want to launch the software from time to time while changing stuff
588
        if f._type == 'selection':
3579.1.12 by Vo Minh Thu
[IMP] orm: get_pg_type() does not use early return.
589
            pg_type = ('varchar', pg_varchar())
590
        else:
591
            pg_type = get_pg_type(f, getattr(fields, f._type))
592
    else:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
593
        _logger.warning('%s type not supported!', field_type)
3579.1.12 by Vo Minh Thu
[IMP] orm: get_pg_type() does not use early return.
594
        pg_type = None
595
596
    return pg_type
1 by pinky
New trunk
597
957 by Olivier Laurent
pep8
598
3453.2.6 by Vo Minh Thu
[REF] osv: use a metaclass to discover new models.
599
class MetaModel(type):
600
    """ Metaclass for the Model.
601
602
    This class is used as the metaclass for the Model class to discover
603
    the models defined in a module (i.e. without instanciating them).
604
    If the automatic discovery is not needed, it is possible to set the
605
    model's _register attribute to False.
606
607
    """
608
609
    module_to_models = {}
610
611
    def __init__(self, name, bases, attrs):
612
        if not self._register:
613
            self._register = True
614
            super(MetaModel, self).__init__(name, bases, attrs)
615
            return
616
3943.1.6 by Vo Minh Thu
[IMP] openerp.addons: openerp.addons is used instead of
617
        # The (OpenERP) module name can be in the `openerp.addons` namespace
3943.1.4 by Vo Minh Thu
[FIX] orm: correctly set the module name on the model, even when imported with
618
        # or not. For instance module `sale` can be imported as
3943.1.6 by Vo Minh Thu
[IMP] openerp.addons: openerp.addons is used instead of
619
        # `openerp.addons.sale` (the good way) or `sale` (for backward
3943.1.4 by Vo Minh Thu
[FIX] orm: correctly set the module name on the model, even when imported with
620
        # compatibility).
621
        module_parts = self.__module__.split('.')
622
        if len(module_parts) > 2 and module_parts[0] == 'openerp' and \
3943.1.6 by Vo Minh Thu
[IMP] openerp.addons: openerp.addons is used instead of
623
            module_parts[1] == 'addons':
3943.1.4 by Vo Minh Thu
[FIX] orm: correctly set the module name on the model, even when imported with
624
            module_name = self.__module__.split('.')[2]
625
        else:
626
            module_name = self.__module__.split('.')[0]
3453.2.6 by Vo Minh Thu
[REF] osv: use a metaclass to discover new models.
627
        if not hasattr(self, '_module'):
628
            self._module = module_name
629
630
        # Remember which models to instanciate for this module.
4743.1.11 by Fabien Pinckaers
[IMP] custom model allowed
631
        if not self._custom:
632
            self.module_to_models.setdefault(self._module, []).append(self)
3453.2.6 by Vo Minh Thu
[REF] osv: use a metaclass to discover new models.
633
634
3511.1.32 by Olivier Dony
[MERGE] sync w/ latest trunk (+ fix import cycles)
635
# Definition of log access columns, automatically added to models if
636
# self._log_access is True
637
LOG_ACCESS_COLUMNS = {
638
    'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
639
    'create_date': 'TIMESTAMP',
640
    'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
641
    'write_date': 'TIMESTAMP'
642
}
643
# special columns automatically created by the ORM
644
MAGIC_COLUMNS =  ['id'] + LOG_ACCESS_COLUMNS.keys()
645
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
646
class BaseModel(object):
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
647
    """ Base class for OpenERP models.
648
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
649
    OpenERP models are created by inheriting from this class' subclasses:
650
651
        * Model: for regular database-persisted models
652
        * TransientModel: for temporary data, stored in the database but automatically
653
                          vaccuumed every so often
654
        * AbstractModel: for abstract super classes meant to be shared by multiple
655
                        _inheriting classes (usually Models or TransientModels)
656
657
    The system will later instantiate the class once per database (on
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
658
    which the class' module is installed).
659
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
660
    To create a class that should not be instantiated, the _register class attribute
661
    may be set to False.
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
662
    """
3511.1.8 by Vo Minh Thu
[REF] osv: previous diff makes unneccessary one of makeInstance/createInstance.
663
    __metaclass__ = MetaModel
4344.1.40 by Thibault Delavallée
[IMP] ir.needaction: made the model Abstract. Fixed a bug when inheriting from AbstractModels: _auto (to create a database for the model) was not correctly set.
664
    _auto = True # create database backend
3511.1.8 by Vo Minh Thu
[REF] osv: previous diff makes unneccessary one of makeInstance/createInstance.
665
    _register = False # Set to false if the model shouldn't be automatically discovered.
922 by Christophe Simonis
convert tabs to 4 spaces
666
    _name = None
667
    _columns = {}
668
    _constraints = []
4743.1.11 by Fabien Pinckaers
[IMP] custom model allowed
669
    _custom = False
922 by Christophe Simonis
convert tabs to 4 spaces
670
    _defaults = {}
4272.1.5 by Antony Lesuisse
[FIX] typos and vmt review
671
    _rec_name = None
922 by Christophe Simonis
convert tabs to 4 spaces
672
    _parent_name = 'parent_id'
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
673
    _parent_store = False
1595.1.7 by Fabien Pinckaers
bugfixes
674
    _parent_order = False
922 by Christophe Simonis
convert tabs to 4 spaces
675
    _date_name = 'date'
676
    _order = 'id'
677
    _sequence = None
678
    _description = None
4333.1.1 by Fabien Pinckaers
[IMP] cleaning of need action
679
    _needaction = False
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
680
4399 by Fabien Pinckaers
[IMP] folded columns in read_group
681
    # dict of {field:method}, with method returning the (name_get of records, {id: fold})
3791 by Fabien Pinckaers
[IMP] _group_by_full implementation
682
    # to include in the _read_group, if grouped on this field
683
    _group_by_full = {}
684
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
685
    # Transience
686
    _transient = False # True in a TransientModel
687
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
688
    # structure:
689
    #  { 'parent_model': 'm2o_field', ... }
922 by Christophe Simonis
convert tabs to 4 spaces
690
    _inherits = {}
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
691
3627 by Vo Minh Thu
[MERGE] orm: properly handle multi-level _inherits.
692
    # Mapping from inherits'd field name to triple (m, r, f, n) where m is the
693
    # model from which it is inherits'd, r is the (local) field towards m, f
694
    # is the _column object itself, and n is the original (i.e. top-most)
695
    # parent model.
696
    # Example:
697
    #  { 'field_name': ('parent_model', 'm2o_field_to_reach_parent',
698
    #                   field_column_obj, origina_parent_model), ... }
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
699
    _inherit_fields = {}
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
700
3466.1.8 by Vo Minh Thu
[REF] orm: add a column_info class to represent entries in _inherit_fields,
701
    # Mapping field name/column_info object
702
    # This is similar to _inherit_fields but:
703
    # 1. includes self fields,
704
    # 2. uses column_info instead of a triple.
705
    _all_columns = {}
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
706
922 by Christophe Simonis
convert tabs to 4 spaces
707
    _table = None
1294 by Christophe Simonis
[IMP] better get_invalid_fields function
708
    _invalids = set()
2265 by Fabien Pinckaers
[FIX] Improve logging system
709
    _log_create = False
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
710
    _sql_constraints = []
711
    _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']
712
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
713
    CONCURRENCY_CHECK_FIELD = '__last_update'
3647 by Olivier Dony
[FIX] orm,expression: sanity checks for domain terms, cleanup, tests
714
2265 by Fabien Pinckaers
[FIX] Improve logging system
715
    def log(self, cr, uid, id, message, secondary=False, context=None):
4071.2.26 by Thibault Delavallée
[REM] Removed res.log feature in ORM. However, the res.log table stays, because it is still used notably in publisher_warranty, that must be checked to see how it should be updated with OpenChatter.
716
        return _logger.warning("log() is deprecated. Please use OpenChatter notification system instead of the res.log mechanism.")
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
717
2537.1.2 by Numerigraphe - Lionel Sausin
[FIX] mutable default in osv
718
    def view_init(self, cr, uid, fields_list, context=None):
2111.1.15 by Rvo(Open ERP)
[IMP]:allowed default_get method to throw exception for osv_memory wizards
719
        """Override this method to do specific things when a view on the object is opened."""
720
        pass
721
2537.1.2 by Numerigraphe - Lionel Sausin
[FIX] mutable default in osv
722
    def _field_create(self, cr, context=None):
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
723
        """ Create entries in ir_model_fields for all the model's fields.
3432.1.4 by Vo Minh Thu
[IMP] osv: removed unnecessary module arg, added comments.
724
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
725
        If necessary, also create an entry in ir_model, and if called from the
726
        modules loading scheme (by receiving 'module' in the context), also
727
        create entries in ir_model_data (for the model and the fields).
3432.1.4 by Vo Minh Thu
[IMP] osv: removed unnecessary module arg, added comments.
728
729
        - create an entry in ir_model (if there is not already one),
730
        - create an entry in ir_model_data (if there is not already one, and if
731
          'module' is in the context),
732
        - update ir_model_fields with the fields found in _columns
733
          (TODO there is some redundancy as _columns is updated from
734
          ir_model_fields in __init__).
735
736
        """
2537.1.2 by Numerigraphe - Lionel Sausin
[FIX] mutable default in osv
737
        if context is None:
738
            context = {}
1460 by Christophe Simonis
[IMP] "not x in l" -> "x not in l"
739
        cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
922 by Christophe Simonis
convert tabs to 4 spaces
740
        if not cr.rowcount:
741
            cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
1307 by Fabien Pinckaers
improved_customization
742
            model_id = cr.fetchone()[0]
1460 by Christophe Simonis
[IMP] "not x in l" -> "x not in l"
743
            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'))
1341.4.2 by Fabien Pinckaers
modifs
744
        else:
745
            model_id = cr.fetchone()[0]
746
        if 'module' in context:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
747
            name_id = 'model_'+self._name.replace('.', '_')
3272 by Fabien Pinckaers
[IMP] logging for set null failed
748
            cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
1341.4.2 by Fabien Pinckaers
modifs
749
            if not cr.rowcount:
4066.1.3 by Olivier Dony
[FIX] Correct remaining SQL now() calls, must use UTC
750
                cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, (now() at time zone 'UTC'), (now() at time zone 'UTC'), %s, %s, %s)", \
1341.4.2 by Fabien Pinckaers
modifs
751
                    (name_id, context['module'], 'ir.model', model_id)
922 by Christophe Simonis
convert tabs to 4 spaces
752
                )
1341.4.2 by Fabien Pinckaers
modifs
753
922 by Christophe Simonis
convert tabs to 4 spaces
754
        cr.commit()
755
756
        cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,))
757
        cols = {}
758
        for rec in cr.dictfetchall():
759
            cols[rec['name']] = rec
760
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
761
        ir_model_fields_obj = self.pool.get('ir.model.fields')
3808.1.2 by Olivier Dony
[IMP] orm,fields: some cleanup for sparse field implementation (wip)
762
763
        # sparse field should be created at the end, as it depends on its serialized field already existing
3808.1.5 by Olivier Dony
[FIX] orm: typos
764
        model_fields = sorted(self._columns.items(), key=lambda x: 1 if x[1]._type == 'sparse' else 0)
765
        for (k, f) in model_fields:
922 by Christophe Simonis
convert tabs to 4 spaces
766
            vals = {
957 by Olivier Laurent
pep8
767
                'model_id': model_id,
768
                'model': self._name,
769
                'name': k,
4263.1.2 by Xavier Morel
[REM] orm: pointless character replacement of quotes by spaces in field strings
770
                'field_description': f.string,
957 by Olivier Laurent
pep8
771
                'ttype': f._type,
2238.1.5 by Anup(OpenERP)
[FIX] Copy() of ir_model_fields corected
772
                'relation': f._obj or '',
957 by Olivier Laurent
pep8
773
                'view_load': (f.view_load and 1) or 0,
1844.4.57 by RGA(OpenERP)
[FIX] Manual object and field creation with searchable
774
                'select_level': tools.ustr(f.select or 0),
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
775
                'readonly': (f.readonly and 1) or 0,
776
                'required': (f.required and 1) or 0,
777
                'selectable': (f.selectable and 1) or 0,
3310.28.1 by olt at tinyerp
[FIX] fixes launchpad bug 780584: Field definition translate=Yes is not stored in ir.model.fields
778
                'translate': (f.translate and 1) or 0,
4263.1.3 by Xavier Morel
[REM] redundant code: a field of type one2many will always have _type one2many
779
                'relation_field': f._fields_id if isinstance(f, fields.one2many) else '',
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
780
                'serialization_field_id': None,
922 by Christophe Simonis
convert tabs to 4 spaces
781
            }
3808.1.2 by Olivier Dony
[IMP] orm,fields: some cleanup for sparse field implementation (wip)
782
            if getattr(f, 'serialization_field', None):
783
                # resolve link to serialization_field if specified by name
4376 by Stephane Wirtel
[REF] Replace the user id 1 by openerp.SUPERUSER_ID
784
                serialization_field_id = ir_model_fields_obj.search(cr, SUPERUSER_ID, [('model','=',vals['model']), ('name', '=', f.serialization_field)])
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
785
                if not serialization_field_id:
3808.1.2 by Olivier Dony
[IMP] orm,fields: some cleanup for sparse field implementation (wip)
786
                    raise except_orm(_('Error'), _("Serialization field `%s` not found for sparse field `%s`!") % (f.serialization_field, k))
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
787
                vals['serialization_field_id'] = serialization_field_id[0]
3808.1.2 by Olivier Dony
[IMP] orm,fields: some cleanup for sparse field implementation (wip)
788
1844.4.57 by RGA(OpenERP)
[FIX] Manual object and field creation with searchable
789
            # When its a custom field,it does not contain f.select
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
790
            if context.get('field_state', 'base') == 'manual':
791
                if context.get('field_name', '') == k:
792
                    vals['select_level'] = context.get('select', '0')
1844.4.57 by RGA(OpenERP)
[FIX] Manual object and field creation with searchable
793
                #setting value to let the problem NOT occur next time
2700 by Jay(OpenERP)
[FIX] Custom field may not be found on the first go
794
                elif k in cols:
1844.4.57 by RGA(OpenERP)
[FIX] Manual object and field creation with searchable
795
                    vals['select_level'] = cols[k]['select_level']
1869.1.24 by nch at tinyerp
[MERGE]:from parent
796
922 by Christophe Simonis
convert tabs to 4 spaces
797
            if k not in cols:
798
                cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
799
                id = cr.fetchone()[0]
800
                vals['id'] = id
801
                cr.execute("""INSERT INTO ir_model_fields (
957 by Olivier Laurent
pep8
802
                    id, model_id, model, name, field_description, ttype,
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
803
                    relation,view_load,state,select_level,relation_field, translate, serialization_field_id
922 by Christophe Simonis
convert tabs to 4 spaces
804
                ) VALUES (
3569.1.3 by sebastien beau
[IMP] add the posibility to create sparse field dynamically
805
                    %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
922 by Christophe Simonis
convert tabs to 4 spaces
806
                )""", (
807
                    id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
1192 by Fabien Pinckaers
improve
808
                     vals['relation'], bool(vals['view_load']), 'base',
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
809
                    vals['select_level'], vals['relation_field'], bool(vals['translate']), vals['serialization_field_id']
922 by Christophe Simonis
convert tabs to 4 spaces
810
                ))
811
                if 'module' in context:
1803.1.23 by ACH(OpenERP)
[FIX] ir_model_data : SQL_Constraint failure covered
812
                    name1 = 'field_' + self._table + '_' + k
1803.1.34 by Christophe Simonis
[FIX] increase size of field "name" of ir.model.data
813
                    cr.execute("select name from ir_model_data where name=%s", (name1,))
1803.1.23 by ACH(OpenERP)
[FIX] ir_model_data : SQL_Constraint failure covered
814
                    if cr.fetchone():
815
                        name1 = name1 + "_" + str(id)
4066.1.3 by Olivier Dony
[FIX] Correct remaining SQL now() calls, must use UTC
816
                    cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, (now() at time zone 'UTC'), (now() at time zone 'UTC'), %s, %s, %s)", \
1803.1.34 by Christophe Simonis
[FIX] increase size of field "name" of ir.model.data
817
                        (name1, context['module'], 'ir.model.fields', id)
922 by Christophe Simonis
convert tabs to 4 spaces
818
                    )
819
            else:
957 by Olivier Laurent
pep8
820
                for key, val in vals.items():
821
                    if cols[k][key] != vals[key]:
822
                        cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
922 by Christophe Simonis
convert tabs to 4 spaces
823
                        cr.commit()
824
                        cr.execute("""UPDATE ir_model_fields SET
1192 by Fabien Pinckaers
improve
825
                            model_id=%s, field_description=%s, ttype=%s, relation=%s,
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
826
                            view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s, serialization_field_id=%s
922 by Christophe Simonis
convert tabs to 4 spaces
827
                        WHERE
828
                            model=%s AND name=%s""", (
1242 by hda at tinyerp
bugfix for active=false search
829
                                vals['model_id'], vals['field_description'], vals['ttype'],
1009.1.1 by mga at tinyerp
Bug-Fix
830
                                vals['relation'], bool(vals['view_load']),
3569.1.4 by sebastien beau
[REF] ir_model_field replace the serialisation_field by serialization_field_id
831
                                vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['serialization_field_id'], vals['model'], vals['name']
922 by Christophe Simonis
convert tabs to 4 spaces
832
                            ))
2967 by Fabien Pinckaers
[IMP] Speed improvement when loading modules
833
                        break
922 by Christophe Simonis
convert tabs to 4 spaces
834
        cr.commit()
835
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
836
    #
837
    # Goal: try to apply inheritance at the instanciation level and
838
    #       put objects in the pool var
839
    #
840
    @classmethod
3511.1.8 by Vo Minh Thu
[REF] osv: previous diff makes unneccessary one of makeInstance/createInstance.
841
    def create_instance(cls, pool, cr):
3432.1.4 by Vo Minh Thu
[IMP] osv: removed unnecessary module arg, added comments.
842
        """ Instanciate a given model.
843
844
        This class method instanciates the class of some model (i.e. a class
845
        deriving from osv or osv_memory). The class might be the class passed
846
        in argument or, if it inherits from another class, a class constructed
847
        by combining the two classes.
848
849
        The ``attributes`` argument specifies which parent class attributes
850
        have to be combined.
851
852
        TODO: the creation of the combined class is repeated at each call of
853
        this method. This is probably unnecessary.
854
855
        """
3511.1.8 by Vo Minh Thu
[REF] osv: previous diff makes unneccessary one of makeInstance/createInstance.
856
        attributes = ['_columns', '_defaults', '_inherits', '_constraints',
857
            '_sql_constraints']
858
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
859
        parent_names = getattr(cls, '_inherit', None)
860
        if parent_names:
861
            if isinstance(parent_names, (str, unicode)):
862
                name = cls._name or parent_names
863
                parent_names = [parent_names]
864
            else:
865
                name = cls._name
866
            if not name:
867
                raise TypeError('_name is mandatory in case of multiple inheritance')
868
869
            for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
3427.2.41 by Olivier Dony
[IMP] orm: remember Model._original_module when constructing inheritance
870
                parent_model = pool.get(parent_name)
871
                if not parent_model:
3432.1.4 by Vo Minh Thu
[IMP] osv: removed unnecessary module arg, added comments.
872
                    raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
873
                        'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
4066.1.29 by olt at tinyerp
[FIX] 'create_instance' method: the check for parent_model existence need to be done before accessing that object attributes or methods
874
                if not getattr(cls, '_original_module', None) and name == parent_model._name:
875
                    cls._original_module = parent_model._original_module
3427.2.41 by Olivier Dony
[IMP] orm: remember Model._original_module when constructing inheritance
876
                parent_class = parent_model.__class__
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
877
                nattr = {}
878
                for s in attributes:
3427.2.41 by Olivier Dony
[IMP] orm: remember Model._original_module when constructing inheritance
879
                    new = copy.copy(getattr(parent_model, s, {}))
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
880
                    if s == '_columns':
881
                        # Don't _inherit custom fields.
882
                        for c in new.keys():
883
                            if new[c].manual:
884
                                del new[c]
4062 by Vo Minh Thu
[FIX] fields: duplicate float fields per registry (because they are stateful).
885
                        # Duplicate float fields because they have a .digits
886
                        # cache (which must be per-registry, not server-wide).
887
                        for c in new.keys():
888
                            if new[c]._type == 'float':
889
                                new[c] = copy.copy(new[c])
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
890
                    if hasattr(new, 'update'):
891
                        new.update(cls.__dict__.get(s, {}))
892
                    elif s=='_constraints':
893
                        for c in cls.__dict__.get(s, []):
894
                            exist = False
895
                            for c2 in range(len(new)):
3974.2.2 by Florent Xicluna
[REF] fix weird indentation, not multiple of four.
896
                                #For _constraints, we should check field and methods as well
897
                                if new[c2][2]==c[2] and (new[c2][0] == c[0] \
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
898
                                        or getattr(new[c2][0],'__name__', True) == \
899
                                            getattr(c[0],'__name__', False)):
900
                                    # If new class defines a constraint with
901
                                    # same function name, we let it override
902
                                    # the old one.
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
903
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
904
                                    new[c2] = c
905
                                    exist = True
906
                                    break
907
                            if not exist:
908
                                new.append(c)
909
                    else:
910
                        new.extend(cls.__dict__.get(s, []))
911
                    nattr[s] = new
4633 by Olivier Dony
[FIX] translate,orm: remember local Model [_sql]_constraints, and keep only those when exporting translations
912
913
                # Keep links to non-inherited constraints, e.g. useful when exporting translations
914
                nattr['_local_constraints'] = cls.__dict__.get('_constraints', [])
915
                nattr['_local_sql_constraints'] = cls.__dict__.get('_sql_constraints', [])
916
3453.2.6 by Vo Minh Thu
[REF] osv: use a metaclass to discover new models.
917
                cls = type(name, (cls, parent_class), dict(nattr, _register=False))
4633 by Olivier Dony
[FIX] translate,orm: remember local Model [_sql]_constraints, and keep only those when exporting translations
918
        else:
919
            cls._local_constraints = getattr(cls, '_constraints', [])
920
            cls._local_sql_constraints = getattr(cls, '_sql_constraints', [])
921
3427.2.41 by Olivier Dony
[IMP] orm: remember Model._original_module when constructing inheritance
922
        if not getattr(cls, '_original_module', None):
923
            cls._original_module = cls._module
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
924
        obj = object.__new__(cls)
925
        obj.__init__(pool, cr)
926
        return obj
927
928
    def __new__(cls):
3555 by Olivier Dony
[FIX] registry: use a unique list of models to load per module
929
        """Register this model.
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
930
931
        This doesn't create an instance but simply register the model
932
        as being part of the module where it is defined.
933
934
        """
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
935
936
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
937
        # Set the module name (e.g. base, sale, accounting, ...) on the class.
938
        module = cls.__module__.split('.')[0]
939
        if not hasattr(cls, '_module'):
940
            cls._module = module
941
3555 by Olivier Dony
[FIX] registry: use a unique list of models to load per module
942
        # Record this class in the list of models to instantiate for this module,
943
        # managed by the metaclass.
944
        module_model_list = MetaModel.module_to_models.setdefault(cls._module, [])
945
        if cls not in module_model_list:
4743.1.11 by Fabien Pinckaers
[IMP] custom model allowed
946
            if not cls._custom:
947
                module_model_list.append(cls)
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
948
949
        # Since we don't return an instance here, the __init__
950
        # method won't be called.
951
        return None
952
953
    def __init__(self, pool, cr):
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
954
        """ Initialize a model and make it part of the given registry.
955
956
        - copy the stored fields' functions in the osv_pool,
957
        - update the _columns with the fields found in ir_model_fields,
958
        - ensure there is a many2one for each _inherits'd parent,
959
        - update the children's _columns,
960
        - give a chance to each field to initialize itself.
961
962
        """
3432.1.3 by Vo Minh Thu
[FIX] osv_pool: it appears it is necessary to have the model in the pool earlier on.
963
        pool.add(self._name, self)
3432.1.1 by Vo Minh Thu
[REF] osv: osv classes merged inside orm classes.
964
        self.pool = pool
965
1179 by Stephane Wirtel
Show an error and raise an exception if the _name is not present in an object
966
        if not self._name and not hasattr(self, '_inherit'):
967
            name = type(self).__name__.split('.')[0]
968
            msg = "The class %s has to have a _name attribute" % name
969
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
970
            _logger.error(msg)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
971
            raise except_orm('ValueError', msg)
1179 by Stephane Wirtel
Show an error and raise an exception if the _name is not present in an object
972
922 by Christophe Simonis
convert tabs to 4 spaces
973
        if not self._description:
974
            self._description = self._name
975
        if not self._table:
957 by Olivier Laurent
pep8
976
            self._table = self._name.replace('.', '_')
922 by Christophe Simonis
convert tabs to 4 spaces
977
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
978
        if not hasattr(self, '_log_access'):
979
            # If _log_access is not specified, it is the same value as _auto.
980
            self._log_access = getattr(self, "_auto", True)
981
982
        self._columns = self._columns.copy()
983
        for store_field in self._columns:
984
            f = self._columns[store_field]
985
            if hasattr(f, 'digits_change'):
986
                f.digits_change(cr)
3663.1.2 by Vo Minh Thu
[MERGE] merged trunk.
987
            def not_this_field(stored_func):
988
                x, y, z, e, f, l = stored_func
989
                return x != self._name or y != store_field
990
            self.pool._store_function[self._name] = filter(not_this_field, self.pool._store_function.get(self._name, []))
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
991
            if not isinstance(f, fields.function):
992
                continue
993
            if not f.store:
994
                continue
3663.1.2 by Vo Minh Thu
[MERGE] merged trunk.
995
            sm = f.store
996
            if sm is True:
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
997
                sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
998
            for object, aa in sm.items():
999
                if len(aa) == 4:
1000
                    (fnct, fields2, order, length) = aa
1001
                elif len(aa) == 3:
1002
                    (fnct, fields2, order) = aa
1003
                    length = None
1004
                else:
1005
                    raise except_orm('Error',
1006
                        ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
1007
                self.pool._store_function.setdefault(object, [])
3663.1.2 by Vo Minh Thu
[MERGE] merged trunk.
1008
                self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
1009
                self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
1010
1011
        for (key, _, msg) in self._sql_constraints:
1012
            self.pool._sql_error[self._table+'_'+key] = msg
1013
1014
        # Load manual fields
1015
4672.3.2 by Vo Minh Thu
[IMP] module loading: removed unnecessary indentation, added comments.
1016
        # Check the query is already done for all modules of if we need to
1017
        # do it ourselves.
4672.3.3 by Vo Minh Thu
[FIX] registry: Set the fields_by_model attribute in __init__(), use None to flag non-existing fields-per-model cache.
1018
        if self.pool.fields_by_model is not None:
4672.3.2 by Vo Minh Thu
[IMP] module loading: removed unnecessary indentation, added comments.
1019
            manual_fields = self.pool.fields_by_model.get(self._name, [])
1020
        else:
1021
            cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
1022
            manual_fields = cr.dictfetchall()
1023
        for field in manual_fields:
1024
            if field['name'] in self._columns:
1025
                continue
1026
            attrs = {
1027
                'string': field['field_description'],
1028
                'required': bool(field['required']),
1029
                'readonly': bool(field['readonly']),
1030
                'domain': eval(field['domain']) if field['domain'] else None,
4743.21.1 by Chris Biersbach
[FIX] Removes the limit of 64 characters from additional text fields added via the interface
1031
                'size': field['size'] or None,
4672.3.2 by Vo Minh Thu
[IMP] module loading: removed unnecessary indentation, added comments.
1032
                'ondelete': field['on_delete'],
1033
                'translate': (field['translate']),
1034
                'manual': True,
1035
                #'select': int(field['select_level'])
1036
            }
1037
1038
            if field['serialization_field_id']:
1039
                cr.execute('SELECT name FROM ir_model_fields WHERE id=%s', (field['serialization_field_id'],))
1040
                attrs.update({'serialization_field': cr.fetchone()[0], 'type': field['ttype']})
1041
                if field['ttype'] in ['many2one', 'one2many', 'many2many']:
1042
                    attrs.update({'relation': field['relation']})
1043
                self._columns[field['name']] = fields.sparse(**attrs)
1044
            elif field['ttype'] == 'selection':
1045
                self._columns[field['name']] = fields.selection(eval(field['selection']), **attrs)
1046
            elif field['ttype'] == 'reference':
1047
                self._columns[field['name']] = fields.reference(selection=eval(field['selection']), **attrs)
1048
            elif field['ttype'] == 'many2one':
1049
                self._columns[field['name']] = fields.many2one(field['relation'], **attrs)
1050
            elif field['ttype'] == 'one2many':
1051
                self._columns[field['name']] = fields.one2many(field['relation'], field['relation_field'], **attrs)
1052
            elif field['ttype'] == 'many2many':
1053
                _rel1 = field['relation'].replace('.', '_')
1054
                _rel2 = field['model'].replace('.', '_')
1055
                _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
1056
                self._columns[field['name']] = fields.many2many(field['relation'], _rel_name, 'id1', 'id2', **attrs)
4672.3.1 by Vo Minh Thu
[IMP] Reduce considerably the loading time of a new registry.
1057
            else:
4672.3.2 by Vo Minh Thu
[IMP] module loading: removed unnecessary indentation, added comments.
1058
                self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
1059
1060
        self._inherits_check()
1061
        self._inherits_reload()
1062
        if not self._sequence:
1063
            self._sequence = self._table + '_id_seq'
1064
        for k in self._defaults:
1065
            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,)
1066
        for f in self._columns:
1067
            self._columns[f].restart()
1068
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
1069
        # Transience
1070
        if self.is_transient():
1071
            self._transient_check_count = 0
1072
            self._transient_max_count = config.get('osv_memory_count_limit')
1073
            self._transient_max_hours = config.get('osv_memory_age_limit')
1074
            assert self._log_access, "TransientModels must have log_access turned on, "\
1075
                                     "in order to implement their access rights policy"
922 by Christophe Simonis
convert tabs to 4 spaces
1076
4272.1.1 by Antony Lesuisse
[IMP] rec_name assertion and fallback
1077
        # Validate rec_name
4272.1.6 by Vo Minh Thu
[MAOW] lolcat ninja fix in the branch, yaoh!
1078
        if self._rec_name is not None:
4629.1.1 by ajay javiya (OpenERP)
[FIX]:Issue of add custome field on object
1079
            assert self._rec_name in self._all_columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name)
4272.1.1 by Antony Lesuisse
[IMP] rec_name assertion and fallback
1080
        else:
1081
            self._rec_name = 'name'
1082
1083
922 by Christophe Simonis
convert tabs to 4 spaces
1084
    def __export_row(self, cr, uid, row, fields, context=None):
2238.1.41 by Jay(Open ERP)
[FIX] Export should honor the current locale(language) settings of the user.
1085
        if context is None:
1086
            context = {}
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1087
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1088
        def check_type(field_type):
1089
            if field_type == 'float':
1800.2.7 by ACH,JVO
[FIX] Export will consider float values as float and char as char
1090
                return 0.0
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1091
            elif field_type == 'integer':
1800.2.7 by ACH,JVO
[FIX] Export will consider float values as float and char as char
1092
                return 0
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1093
            elif field_type == 'boolean':
3549.3.1 by tfr(OpenERP)
[FIX]:import boolean fields
1094
                return 'False'
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1095
            return ''
1869.1.14 by nch at tinyerp
read_group method for group by
1096
1844.4.5 by YSA,JVO
[FIX] Export : Selection field gets imported by its external name if export is not import compatible
1097
        def selection_field(in_field):
1098
            col_obj = self.pool.get(in_field.keys()[0])
1099
            if f[i] in col_obj._columns.keys():
1100
                return  col_obj._columns[f[i]]
1101
            elif f[i] in col_obj._inherits.keys():
1102
                selection_field(col_obj._inherits)
1103
            else:
1869.1.14 by nch at tinyerp
read_group method for group by
1104
                return False
1105
3927 by Fabien Pinckaers
[FIX] import/export fully working through web interface
1106
        def _get_xml_id(self, cr, uid, r):
1107
            model_data = self.pool.get('ir.model.data')
1108
            data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
1109
            if len(data_ids):
1110
                d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
1111
                if d['module']:
1112
                    r = '%s.%s' % (d['module'], d['name'])
1113
                else:
1114
                    r = d['name']
1115
            else:
1116
                postfix = 0
1117
                while True:
1118
                    n = self._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
1119
                    if not model_data.search(cr, uid, [('name', '=', n)]):
1120
                        break
1121
                    postfix += 1
4066.1.134 by Olivier Dony
[FIX] export_data: avoid access error while auto-generating external IDs during export
1122
                model_data.create(cr, SUPERUSER_ID, {
3927 by Fabien Pinckaers
[FIX] import/export fully working through web interface
1123
                    'name': n,
1124
                    'model': self._name,
1125
                    'res_id': r['id'],
1126
                    'module': '__export__',
1127
                })
1128
                r = '__export__.'+n
1129
            return r
1130
922 by Christophe Simonis
convert tabs to 4 spaces
1131
        lines = []
1132
        data = map(lambda x: '', range(len(fields)))
3014 by Anup(OpenERP)
[FIX](Maintenance) Exportation fixed for o2m and m2m
1133
        done = []
922 by Christophe Simonis
convert tabs to 4 spaces
1134
        for fpos in range(len(fields)):
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1135
            f = fields[fpos]
922 by Christophe Simonis
convert tabs to 4 spaces
1136
            if f:
1137
                r = row
1138
                i = 0
957 by Olivier Laurent
pep8
1139
                while i < len(f):
3927 by Fabien Pinckaers
[FIX] import/export fully working through web interface
1140
                    cols = False
3130 by Fabien Pinckaers
[IMP] import / export cleaned
1141
                    if f[i] == '.id':
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1142
                        r = r['id']
1143
                    elif f[i] == 'id':
3927 by Fabien Pinckaers
[FIX] import/export fully working through web interface
1144
                        r = _get_xml_id(self, cr, uid, r)
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1145
                    else:
1844.4.5 by YSA,JVO
[FIX] Export : Selection field gets imported by its external name if export is not import compatible
1146
                        r = r[f[i]]
1147
                        # To display external name of selection field when its exported
3130 by Fabien Pinckaers
[IMP] import / export cleaned
1148
                        if f[i] in self._columns.keys():
1149
                            cols = self._columns[f[i]]
1150
                        elif f[i] in self._inherit_fields.keys():
1151
                            cols = selection_field(self._inherits)
1152
                        if cols and cols._type == 'selection':
1153
                            sel_list = cols.selection
1154
                            if r and type(sel_list) == type([]):
1155
                                r = [x[1] for x in sel_list if r==x[0]]
1156
                                r = r and r[0] or False
922 by Christophe Simonis
convert tabs to 4 spaces
1157
                    if not r:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1158
                        if f[i] in self._columns:
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1159
                            r = check_type(self._columns[f[i]]._type)
1800.2.7 by ACH,JVO
[FIX] Export will consider float values as float and char as char
1160
                        elif f[i] in self._inherit_fields:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1161
                            r = check_type(self._inherit_fields[f[i]][2]._type)
3138 by Fabien Pinckaers
[FIX] import / export
1162
                        data[fpos] = r or False
922 by Christophe Simonis
convert tabs to 4 spaces
1163
                        break
1164
                    if isinstance(r, (browse_record_list, list)):
1165
                        first = True
1166
                        fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \
1167
                                or [], fields)
1168
                        if fields2 in done:
3014 by Anup(OpenERP)
[FIX](Maintenance) Exportation fixed for o2m and m2m
1169
                            if [x for x in fields2 if x]:
1170
                                break
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1171
                        done.append(fields2)
3927 by Fabien Pinckaers
[FIX] import/export fully working through web interface
1172
                        if cols and cols._type=='many2many' and len(fields[fpos])>(i+1) and (fields[fpos][i+1]=='id'):
1173
                            data[fpos] = ','.join([_get_xml_id(self, cr, uid, x) for x in r])
1174
                            break
1175
922 by Christophe Simonis
convert tabs to 4 spaces
1176
                        for row2 in r:
3927 by Fabien Pinckaers
[FIX] import/export fully working through web interface
1177
                            lines2 = row2._model.__export_row(cr, uid, row2, fields2,
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1178
                                    context)
922 by Christophe Simonis
convert tabs to 4 spaces
1179
                            if first:
1180
                                for fpos2 in range(len(fields)):
1181
                                    if lines2 and lines2[0][fpos2]:
1182
                                        data[fpos2] = lines2[0][fpos2]
1803.1.2 by Harry (Open ERP)
[FIX] export window : use context for import compatable option in pass argument instend of added new argemenent
1183
                                if not data[fpos]:
1184
                                    dt = ''
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
1185
                                    for rr in r:
3310.1.19 by Jay Vora(OpenERP)
[FIX] Export : _rec_name should be used instead of hardcoded name for relational records (Ref: SaaS Maintenance case 3921)
1186
                                        name_relation = self.pool.get(rr._table_name)._rec_name
1187
                                        if isinstance(rr[name_relation], browse_record):
1188
                                            rr = rr[name_relation]
2238.1.41 by Jay(Open ERP)
[FIX] Export should honor the current locale(language) settings of the user.
1189
                                        rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
1844.6.2 by HDA(OpenERP)
Merge and bugfixes from jvo
1190
                                        rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
1844.6.4 by Jay(Open ERP)
[FIX] Export : M2M field name was missing last character
1191
                                        dt += tools.ustr(rr_name or '') + ','
1803.1.2 by Harry (Open ERP)
[FIX] export window : use context for import compatable option in pass argument instend of added new argemenent
1192
                                    data[fpos] = dt[:-1]
1193
                                    break
957 by Olivier Laurent
pep8
1194
                                lines += lines2[1:]
922 by Christophe Simonis
convert tabs to 4 spaces
1195
                                first = False
1196
                            else:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1197
                                lines += lines2
922 by Christophe Simonis
convert tabs to 4 spaces
1198
                        break
957 by Olivier Laurent
pep8
1199
                    i += 1
1200
                if i == len(f):
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1201
                    if isinstance(r, browse_record):
2238.1.41 by Jay(Open ERP)
[FIX] Export should honor the current locale(language) settings of the user.
1202
                        r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
1844.6.2 by HDA(OpenERP)
Merge and bugfixes from jvo
1203
                        r = r and r[0] and r[0][1] or ''
1597.1.1 by Stephane Wirtel
[FIX] Encoding error
1204
                    data[fpos] = tools.ustr(r or '')
922 by Christophe Simonis
convert tabs to 4 spaces
1205
        return [data] + lines
1206
1803.1.2 by Harry (Open ERP)
[FIX] export window : use context for import compatable option in pass argument instend of added new argemenent
1207
    def export_data(self, cr, uid, ids, fields_to_export, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
1208
        """
1209
        Export fields for selected objects
1210
1211
        :param cr: database cursor
1212
        :param uid: current user id
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
1213
        :param ids: list of ids
1214
        :param fields_to_export: list of fields
3130 by Fabien Pinckaers
[IMP] import / export cleaned
1215
        :param context: context arguments, like lang, time zone
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
1216
        :rtype: dictionary with a *datas* matrix
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
1217
1218
        This method is used when exporting data via client menu
1219
1220
        """
2605 by Jay(OpenERP)
[REF]
1221
        if context is None:
957 by Olivier Laurent
pep8
1222
            context = {}
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1223
        cols = self._columns.copy()
1224
        for f in self._inherit_fields:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1225
            cols.update({f: self._inherit_fields[f][2]})
3641 by Xavier Morel
[IMP] use same fieldpath-splitting function in data_import and data_export, document it a bit
1226
        fields_to_export = map(fix_import_export_id_paths, fields_to_export)
922 by Christophe Simonis
convert tabs to 4 spaces
1227
        datas = []
1228
        for row in self.browse(cr, uid, ids, context):
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1229
            datas += self.__export_row(cr, uid, row, fields_to_export, context)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
1230
        return {'datas': datas}
922 by Christophe Simonis
convert tabs to 4 spaces
1231
1729.1.17 by Fabien Pinckaers
bugfix_portal_access_rights
1232
    def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
4408.2.21 by Xavier Morel
[DOC] fixes and cleanups
1233
        """
1234
        .. deprecated:: 7.0
1235
            Use :meth:`~load` instead
1236
1237
        Import given data in given module
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
1238
3171 by Olivier Dony
[FIX] orm: corrected RST for docstring of import_data()
1239
        This method is used when importing data via client menu.
1240
1241
        Example of fields to import for a sale.order::
1242
3154 by Fabien Pinckaers
[FIX] Complete recode of import_data to clean and fix export with import compatible.
1243
            .id,                         (=database_id)
1244
            partner_id,                  (=name_search)
1245
            order_line/.id,              (=database_id)
1246
            order_line/name,
1247
            order_line/product_id/id,    (=xml id)
1248
            order_line/price_unit,
1249
            order_line/product_uom_qty,
1250
            order_line/product_uom/id    (=xml_id)
3661 by Xavier Morel
[IMP] doc for import_data
1251
3754 by Olivier Dony
[FIX] Model.import_data docstring: cleanup + clear RST warnings for autodoc
1252
        This method returns a 4-tuple with the following structure::
1253
1254
            (return_code, errored_resource, error_message, unused)
1255
1256
        * The first item is a return code, it is ``-1`` in case of
1257
          import error, or the last imported row number in case of success
1258
        * The second item contains the record data dict that failed to import
1259
          in case of error, otherwise it's 0
1260
        * The third item contains an error message string in case of error,
1261
          otherwise it's 0
1262
        * The last item is currently unused, with no specific semantics
1263
1264
        :param fields: list of fields to import
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1265
        :param datas: data to import
3661 by Xavier Morel
[IMP] doc for import_data
1266
        :param mode: 'init' or 'update' for record creation
1267
        :param current_module: module name
1268
        :param noupdate: flag for record creation
1269
        :param filename: optional file to store partial import state for recovery
3754 by Olivier Dony
[FIX] Model.import_data docstring: cleanup + clear RST warnings for autodoc
1270
        :returns: 4-tuple in the form (return_code, errored_resource, error_message, unused)
1271
        :rtype: (int, dict or 0, str or 0, str or 0)
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
1272
        """
4408.2.20 by Xavier Morel
[IMP] reimplement BaseModel.import_data on top of BaseModel.load
1273
        context = dict(context) if context is not None else {}
1274
        context['_import_current_module'] = current_module
1275
3641 by Xavier Morel
[IMP] use same fieldpath-splitting function in data_import and data_export, document it a bit
1276
        fields = map(fix_import_export_id_paths, fields)
1803.1.1 by Harry (Open ERP)
[IMP] import/export : added Database ID, ID
1277
        ir_model_data_obj = self.pool.get('ir.model.data')
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
1278
4408.2.20 by Xavier Morel
[IMP] reimplement BaseModel.import_data on top of BaseModel.load
1279
        def log(m):
1280
            if m['type'] == 'error':
1281
                raise Exception(m['message'])
1282
3839 by Xavier Morel
[FIX] correctly restore partial import index when restarting an import
1283
        if config.get('import_partial') and filename:
1284
            with open(config.get('import_partial'), 'rb') as partial_import_file:
1285
                data = pickle.load(partial_import_file)
1286
                position = data.get(filename, 0)
1287
4408.2.20 by Xavier Morel
[IMP] reimplement BaseModel.import_data on top of BaseModel.load
1288
        position = 0
1289
        try:
1290
            for res_id, xml_id, res, info in self._convert_records(cr, uid,
1291
                            self._extract_records(cr, uid, fields, datas,
1292
                                                  context=context, log=log),
1293
                            context=context, log=log):
3458.1.2 by Vo Minh Thu
[LINT] removed unused variable, forgot to change two variables in last commit, corrected typo introduced at 3417.6.6.
1294
                ir_model_data_obj._update(cr, uid, self._name,
3155 by Fabien Pinckaers
fix
1295
                     current_module, res, mode=mode, xml_id=xml_id,
1844.1.25 by Jay(Open ERP)
[FIX] Import : Context of the action/screen passed,taken into consideration while importing
1296
                     noupdate=noupdate, res_id=res_id, context=context)
4408.2.20 by Xavier Morel
[IMP] reimplement BaseModel.import_data on top of BaseModel.load
1297
                position = info.get('rows', {}).get('to', 0) + 1
1298
                if config.get('import_partial') and filename and (not (position%100)):
1299
                    with open(config.get('import_partial'), 'rb') as partial_import:
1300
                        data = pickle.load(partial_import)
1301
                    data[filename] = position
1302
                    with open(config.get('import_partial'), 'wb') as partial_import:
1303
                        pickle.dump(data, partial_import)
1304
                    if context.get('defer_parent_store_computation'):
1305
                        self._parent_store_compute(cr)
1306
                    cr.commit()
1307
        except Exception, e:
1308
            cr.rollback()
1309
            return -1, {}, 'Line %d : %s' % (position + 1, tools.ustr(e)), ''
992 by Fabien Pinckaers
Partial Commit
1310
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
1311
        if context.get('defer_parent_store_computation'):
1312
            self._parent_store_compute(cr)
3840 by Xavier Morel
[REM] import_data: unused variables, redundant parens and variables
1313
        return position, 0, 0, 0
922 by Christophe Simonis
convert tabs to 4 spaces
1314
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1315
    def load(self, cr, uid, fields, data, context=None):
1316
        """
4408.2.10 by Xavier Morel
[IMP] allow converters to add data to import messages, formalize message keys
1317
        Attempts to load the data matrix, and returns a list of ids (or
1318
        ``False`` if there was an error and no id could be generated) and a
1319
        list of messages.
1320
4408.2.21 by Xavier Morel
[DOC] fixes and cleanups
1321
        The ids are those of the records created and saved (in database), in
1322
        the same order they were extracted from the file. They can be passed
1323
        directly to :meth:`~read`
1324
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1325
        :param fields: list of fields to import, at the same index as the corresponding data
1326
        :type fields: list(str)
1327
        :param data: row-major matrix of data to import
1328
        :type data: list(list(str))
1329
        :param dict context:
4408.2.11 by Xavier Morel
[IMP] return a dict from Model.load for easier future extensibility (if needed) rather than a tuple.
1330
        :returns: {ids: list(int)|False, messages: [Message]}
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1331
        """
1332
        cr.execute('SAVEPOINT model_load')
1333
        messages = []
1334
1335
        fields = map(fix_import_export_id_paths, fields)
4548 by Xavier Morel
[FIX] nuke ir.model.data caches before starting imports
1336
        ModelData = self.pool['ir.model.data'].clear_caches()
1337
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
1338
        fg = self.fields_get(cr, uid, context=context)
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1339
1340
        mode = 'init'
1341
        current_module = ''
1342
        noupdate = False
1343
1344
        ids = []
1345
        for id, xid, record, info in self._convert_records(cr, uid,
1346
                self._extract_records(cr, uid, fields, data,
1347
                                      context=context, log=messages.append),
1348
                context=context, log=messages.append):
4408.2.23 by Xavier Morel
[FIX] type error when providing a non-integer for a default database id
1349
            try:
1350
                cr.execute('SAVEPOINT model_load_save')
1351
            except psycopg2.InternalError, e:
1352
                # broken transaction, exit and hope the source error was
1353
                # already logged
1354
                if not any(message['type'] == 'error' for message in messages):
1355
                    messages.append(dict(info, type='error',message=
1356
                        u"Unknown database error: '%s'" % e))
1357
                break
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1358
            try:
1359
                ids.append(ModelData._update(cr, uid, self._name,
1360
                     current_module, record, mode=mode, xml_id=xid,
1361
                     noupdate=noupdate, res_id=id, context=context))
1362
                cr.execute('RELEASE SAVEPOINT model_load_save')
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
1363
            except psycopg2.Warning, e:
1364
                messages.append(dict(info, type='warning', message=str(e)))
4552 by Xavier Morel
[IMP] savepoint rollbacks after error transformation/logging of pg errors to still have original query on cursor
1365
                cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1366
            except psycopg2.Error, e:
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
1367
                messages.append(dict(
4408.2.23 by Xavier Morel
[FIX] type error when providing a non-integer for a default database id
1368
                    info, type='error',
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
1369
                    **PGERROR_TO_OE[e.pgcode](self, fg, info, e)))
4552 by Xavier Morel
[IMP] savepoint rollbacks after error transformation/logging of pg errors to still have original query on cursor
1370
                # Failed to write, log to messages, rollback savepoint (to
1371
                # avoid broken transaction) and keep going
1372
                cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1373
        if any(message['type'] == 'error' for message in messages):
1374
            cr.execute('ROLLBACK TO SAVEPOINT model_load')
4408.2.11 by Xavier Morel
[IMP] return a dict from Model.load for easier future extensibility (if needed) rather than a tuple.
1375
            ids = False
1376
        return {'ids': ids, 'messages': messages}
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1377
    def _extract_records(self, cr, uid, fields_, data,
1378
                         context=None, log=lambda a: None):
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1379
        """ Generates record dicts from the data sequence.
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1380
1381
        The result is a generator of dicts mapping field names to raw
1382
        (unconverted, unvalidated) values.
1383
1384
        For relational fields, if sub-fields were provided the value will be
1385
        a list of sub-records
1386
1387
        The following sub-fields may be set on the record (by key):
1388
        * None is the name_get for the record (to use with name_create/name_search)
1389
        * "id" is the External ID for the record
1390
        * ".id" is the Database ID for the record
1391
        """
1392
        columns = dict((k, v.column) for k, v in self._all_columns.iteritems())
1393
        # Fake columns to avoid special cases in extractor
1394
        columns[None] = fields.char('rec_name')
1395
        columns['id'] = fields.char('External ID')
1396
        columns['.id'] = fields.integer('Database ID')
1397
1398
        # m2o fields can't be on multiple lines so exclude them from the
1399
        # is_relational field rows filter, but special-case it later on to
1400
        # be handled with relational fields (as it can have subfields)
1401
        is_relational = lambda field: columns[field]._type in ('one2many', 'many2many', 'many2one')
1402
        get_o2m_values = itemgetter_tuple(
1403
            [index for index, field in enumerate(fields_)
1404
                  if columns[field[0]]._type == 'one2many'])
1405
        get_nono2m_values = itemgetter_tuple(
1406
            [index for index, field in enumerate(fields_)
1407
                  if columns[field[0]]._type != 'one2many'])
1408
        # Checks if the provided row has any non-empty non-relational field
1409
        def only_o2m_values(row, f=get_nono2m_values, g=get_o2m_values):
1410
            return any(g(row)) and not any(f(row))
1411
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1412
        index = 0
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1413
        while True:
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1414
            if index >= len(data): return
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1415
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1416
            row = data[index]
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1417
            # copy non-relational fields to record dict
1418
            record = dict((field[0], value)
1419
                for field, value in itertools.izip(fields_, row)
1420
                if not is_relational(field[0]))
1421
1422
            # Get all following rows which have relational values attached to
1423
            # the current record (no non-relational values)
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1424
            record_span = itertools.takewhile(
1425
                only_o2m_values, itertools.islice(data, index + 1, None))
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1426
            # stitch record row back on for relational fields
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1427
            record_span = list(itertools.chain([row], record_span))
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1428
            for relfield in set(
1429
                    field[0] for field in fields_
1430
                             if is_relational(field[0])):
1431
                column = columns[relfield]
1432
                # FIXME: how to not use _obj without relying on fields_get?
1433
                Model = self.pool[column._obj]
1434
1435
                # get only cells for this sub-field, should be strictly
1436
                # non-empty, field path [None] is for name_get column
1437
                indices, subfields = zip(*((index, field[1:] or [None])
1438
                                           for index, field in enumerate(fields_)
1439
                                           if field[0] == relfield))
1440
1441
                # return all rows which have at least one value for the
1442
                # subfields of relfield
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1443
                relfield_data = filter(any, map(itemgetter_tuple(indices), record_span))
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1444
                record[relfield] = [subrecord
1445
                    for subrecord, _subinfo in Model._extract_records(
1446
                        cr, uid, subfields, relfield_data,
1447
                        context=context, log=log)]
1448
4408.2.14 by Xavier Morel
[FIX] use lists instead of iterators in BaseModel._extract_records
1449
            yield record, {'rows': {
1450
                'from': index,
1451
                'to': index + len(record_span) - 1
1452
            }}
1453
            index += len(record_span)
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1454
    def _convert_records(self, cr, uid, records,
1455
                         context=None, log=lambda a: None):
1456
        """ Converts records from the source iterable (recursive dicts of
1457
        strings) into forms which can be written to the database (via
1458
        self.create or (ir.model.data)._update)
1459
1460
        :returns: a list of triplets of (id, xid, record)
1461
        :rtype: list((int|None, str|None, dict))
1462
        """
4408.2.8 by Xavier Morel
[IMP] return fields_get-style translated field strings (if available) in user-readable warning and error messages from import, rather than logical field names
1463
        if context is None: context = {}
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1464
        Converter = self.pool['ir.fields.converter']
1465
        columns = dict((k, v.column) for k, v in self._all_columns.iteritems())
4408.2.8 by Xavier Morel
[IMP] return fields_get-style translated field strings (if available) in user-readable warning and error messages from import, rather than logical field names
1466
        Translation = self.pool['ir.translation']
1467
        field_names = dict(
1468
            (f, (Translation._get_source(cr, uid, self._name + ',' + f, 'field',
4408.2.39 by Xavier Morel
[IMP] don't pass in a default lang to _get_source if no value is provided in the context, caught by odo
1469
                                         context.get('lang'))
4536 by Xavier Morel
[FIX] recursive conversion of o2ms in import
1470
                 or column.string))
4408.2.8 by Xavier Morel
[IMP] return fields_get-style translated field strings (if available) in user-readable warning and error messages from import, rather than logical field names
1471
            for f, column in columns.iteritems())
4536 by Xavier Morel
[FIX] recursive conversion of o2ms in import
1472
1473
        convert = Converter.for_model(cr, uid, self, context=context)
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1474
4408.2.10 by Xavier Morel
[IMP] allow converters to add data to import messages, formalize message keys
1475
        def _log(base, field, exception):
1476
            type = 'warning' if isinstance(exception, Warning) else 'error'
4536 by Xavier Morel
[FIX] recursive conversion of o2ms in import
1477
            # logs the logical (not human-readable) field name for automated
1478
            # processing of response, but injects human readable in message
1479
            record = dict(base, type=type, field=field,
4408.2.10 by Xavier Morel
[IMP] allow converters to add data to import messages, formalize message keys
1480
                          message=unicode(exception.args[0]) % base)
1481
            if len(exception.args) > 1 and exception.args[1]:
1482
                record.update(exception.args[1])
1483
            log(record)
1484
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1485
        stream = CountingStream(records)
1486
        for record, extras in stream:
1487
            dbid = False
1488
            xid = False
1489
            # name_get/name_create
1490
            if None in record: pass
1491
            # xid
1492
            if 'id' in record:
1493
                xid = record['id']
1494
            # dbid
1495
            if '.id' in record:
1496
                try:
1497
                    dbid = int(record['.id'])
1498
                except ValueError:
1499
                    # in case of overridden id column
1500
                    dbid = record['.id']
1501
                if not self.search(cr, uid, [('id', '=', dbid)], context=context):
1502
                    log(dict(extras,
1503
                        type='error',
1504
                        record=stream.index,
1505
                        field='.id',
1506
                        message=_(u"Unknown database identifier '%s'") % dbid))
1507
                    dbid = False
1508
4536 by Xavier Morel
[FIX] recursive conversion of o2ms in import
1509
            converted = convert(record, lambda field, err:\
1510
                _log(dict(extras, record=stream.index, field=field_names[field]), field, err))
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
1511
1512
            yield dbid, xid, converted, dict(extras, record=stream.index)
1513
2537.1.2 by Numerigraphe - Lionel Sausin
[FIX] mutable default in osv
1514
    def get_invalid_fields(self, cr, uid):
1294 by Christophe Simonis
[IMP] better get_invalid_fields function
1515
        return list(self._invalids)
1292 by Jay Vora
Colored Fields red ,those fail in constraint validation.
1516
922 by Christophe Simonis
convert tabs to 4 spaces
1517
    def _validate(self, cr, uid, ids, context=None):
1518
        context = context or {}
4425.1.1 by Olivier Dony
[IMP] translations: attempt to optimize out _get_source calls when there is not context lang
1519
        lng = context.get('lang')
922 by Christophe Simonis
convert tabs to 4 spaces
1520
        trans = self.pool.get('ir.translation')
1028 by Christophe Simonis
better error message when _constraints fail
1521
        error_msgs = []
922 by Christophe Simonis
convert tabs to 4 spaces
1522
        for constraint in self._constraints:
1523
            fun, msg, fields = constraint
4635 by Vo Minh Thu
[ADD] added comment in _validate() explaining why the context is not passed around when calling _constraints functions.
1524
            # We don't pass around the context here: validation code
1525
            # must always yield the same results.
922 by Christophe Simonis
convert tabs to 4 spaces
1526
            if not fun(self, cr, uid, ids):
2262 by Quentin De Paoli
[IMP] improved constraints: can now pass a callable message function instead of hardcoded message
1527
                # Check presence of __call__ directly instead of using
1528
                # callable() because it will be deprecated as of Python 3.0
2245.1.1 by Quentin De Paoli
[IMP] improved the constraint validation in order to accept multiple message
1529
                if hasattr(msg, '__call__'):
3017.1.12 by P. Christeas
ORM, ir_*: convert standard constraint messages to callables
1530
                    tmp_msg = msg(self, cr, uid, ids, context=context)
1531
                    if isinstance(tmp_msg, tuple):
1532
                        tmp_msg, params = tmp_msg
1533
                        translated_msg = tmp_msg % params
1534
                    else:
1535
                        translated_msg = tmp_msg
2262 by Quentin De Paoli
[IMP] improved constraints: can now pass a callable message function instead of hardcoded message
1536
                else:
4425.1.1 by Olivier Dony
[IMP] translations: attempt to optimize out _get_source calls when there is not context lang
1537
                    translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg)
1028 by Christophe Simonis
better error message when _constraints fail
1538
                error_msgs.append(
1657.1.4 by Jay (Open ERP)
Report_sxw modified for browse_record values in default formats
1539
                        _("Error occurred while validating the field(s) %s: %s") % (','.join(fields), translated_msg)
1045.1.1 by Jay Vora
Bugfix: _inherits
1540
                )
1294 by Christophe Simonis
[IMP] better get_invalid_fields function
1541
                self._invalids.update(fields)
1028 by Christophe Simonis
better error message when _constraints fail
1542
        if error_msgs:
1543
            raise except_orm('ValidateError', '\n'.join(error_msgs))
1293 by Jay Vora
Colored Fields red ,those fail in constraint validation.Modification for Updation of records
1544
        else:
1294 by Christophe Simonis
[IMP] better get_invalid_fields function
1545
            self._invalids.clear()
922 by Christophe Simonis
convert tabs to 4 spaces
1546
1547
    def default_get(self, cr, uid, fields_list, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
1548
        """
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1549
        Returns default values for the fields in fields_list.
2624 by Olivier Dony
[FIX] default_get: fixed RST indentation in docstring
1550
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1551
        :param fields_list: list of fields to get the default values for (example ['field1', 'field2',])
1552
        :type fields_list: list
2976 by Olivier Dony
[ADD] fields.binary: support 'bin_size_XXX' context flags to selectively enable returning size/contents of a binary field - pending API change after 6.0
1553
        :param context: optional context dictionary - it may contains keys for specifying certain options
1554
                        like ``context_lang`` (language) or ``context_tz`` (timezone) to alter the results of the call.
1555
                        It may contain keys in the form ``default_XXX`` (where XXX is a field name), to set
1556
                        or override a default value for a field.
1557
                        A special ``bin_size`` boolean flag may also be passed in the context to request the
1558
                        value of all fields.binary columns to be returned as the size of the binary instead of its
1559
                        contents. This can also be selectively overriden by passing a field-specific flag
1560
                        in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1561
                        Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1562
        :return: dictionary of the default values (set on the object model class, through user preferences, or in the context)
2017.1.3 by Xavier Morel
[imp] add docstring to explain how default_get works/what it does
1563
        """
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1564
        # trigger view init hook
1565
        self.view_init(cr, uid, fields_list, context)
1566
1567
        if not context:
1568
            context = {}
1569
        defaults = {}
1570
2616 by Olivier Dony
[FIX] orm.default_get: fix invalid renaming by broken PyDev refactor
1571
        # get the default values for the inherited fields
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1572
        for t in self._inherits.keys():
1573
            defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
1574
                context))
1575
2616 by Olivier Dony
[FIX] orm.default_get: fix invalid renaming by broken PyDev refactor
1576
        # get the default values defined in the object
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1577
        for f in fields_list:
1578
            if f in self._defaults:
1579
                if callable(self._defaults[f]):
1580
                    defaults[f] = self._defaults[f](self, cr, uid, context)
1581
                else:
1582
                    defaults[f] = self._defaults[f]
1583
1584
            fld_def = ((f in self._columns) and self._columns[f]) \
1585
                    or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
1586
                    or False
1587
1588
            if isinstance(fld_def, fields.property):
1589
                property_obj = self.pool.get('ir.property')
1590
                prop_value = property_obj.get(cr, uid, f, self._name, context=context)
1591
                if prop_value:
1592
                    if isinstance(prop_value, (browse_record, browse_null)):
1593
                        defaults[f] = prop_value.id
1594
                    else:
1595
                        defaults[f] = prop_value
1596
                else:
1597
                    if f not in defaults:
1598
                        defaults[f] = False
1599
2616 by Olivier Dony
[FIX] orm.default_get: fix invalid renaming by broken PyDev refactor
1600
        # get the default values set by the user and override the default
1601
        # values defined in the object
1602
        ir_values_obj = self.pool.get('ir.values')
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1603
        res = ir_values_obj.get(cr, uid, 'default', False, [self._name])
1604
        for id, field, field_value in res:
1605
            if field in fields_list:
1606
                fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
1607
                if fld_def._type == 'many2one':
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1608
                    obj = self.pool.get(fld_def._obj)
1609
                    if not obj.search(cr, uid, [('id', '=', field_value or False)]):
1610
                        continue
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
1611
                if fld_def._type == 'many2many':
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1612
                    obj = self.pool.get(fld_def._obj)
1613
                    field_value2 = []
4576 by Olivier Dony
[IMP] Model.default_get: allows setting user default values of x2m to False (for consistency with write())
1614
                    for i in range(len(field_value or [])):
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1615
                        if not obj.search(cr, uid, [('id', '=',
1616
                            field_value[i])]):
1617
                            continue
1618
                        field_value2.append(field_value[i])
1619
                    field_value = field_value2
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
1620
                if fld_def._type == 'one2many':
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1621
                    obj = self.pool.get(fld_def._obj)
1622
                    field_value2 = []
4576 by Olivier Dony
[IMP] Model.default_get: allows setting user default values of x2m to False (for consistency with write())
1623
                    for i in range(len(field_value or [])):
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1624
                        field_value2.append({})
1625
                        for field2 in field_value[i]:
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
1626
                            if field2 in obj._columns.keys() and obj._columns[field2]._type == 'many2one':
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1627
                                obj2 = self.pool.get(obj._columns[field2]._obj)
1628
                                if not obj2.search(cr, uid,
1629
                                        [('id', '=', field_value[i][field2])]):
1630
                                    continue
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
1631
                            elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type == 'many2one':
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1632
                                obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
1633
                                if not obj2.search(cr, uid,
1634
                                        [('id', '=', field_value[i][field2])]):
1635
                                    continue
1636
                            # TODO add test for many2many and one2many
1637
                            field_value2[i][field2] = field_value[i][field2]
1638
                    field_value = field_value2
1639
                defaults[field] = field_value
1640
2616 by Olivier Dony
[FIX] orm.default_get: fix invalid renaming by broken PyDev refactor
1641
        # get the default values from the context
2615 by Olivier Dony
[REF] orm.default_get: merge duplicated default_get() up into orm_template.
1642
        for key in context or {}:
1643
            if key.startswith('default_') and (key[8:] in fields_list):
1644
                defaults[key[8:]] = context[key]
1645
        return defaults
1646
2498 by Olivier Dony
[IMP] orm.fields_get: renamed read_access parameter to write_access, reflecting its real usage. Also cleaned up some mess leftover by commit 1338 in orm.field_get_keys() + other cleanup
1647
    def fields_get_keys(self, cr, user, context=None):
1648
        res = self._columns.keys()
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
1649
        # TODO I believe this loop can be replace by
1650
        # res.extend(self._inherit_fields.key())
2498 by Olivier Dony
[IMP] orm.fields_get: renamed read_access parameter to write_access, reflecting its real usage. Also cleaned up some mess leftover by commit 1338 in orm.field_get_keys() + other cleanup
1651
        for parent in self._inherits:
1652
            res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1653
        return res
1654
4272.1.1 by Antony Lesuisse
[IMP] rec_name assertion and fallback
1655
    def _rec_name_fallback(self, cr, uid, context=None):
1656
        rec_name = self._rec_name
1657
        if rec_name not in self._columns:
1658
            rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
1659
        return rec_name
1660
922 by Christophe Simonis
convert tabs to 4 spaces
1661
    #
1662
    # Overload this method if you need a window title which depends on the context
1663
    #
1664
    def view_header_get(self, cr, user, view_id=None, view_type='form', context=None):
1665
        return False
1666
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1667
    def user_has_groups(self, cr, uid, groups, context=None):
1668
        """Return true if the user is at least member of one of the groups
1669
           in groups_str. Typically used to resolve ``groups`` attribute
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
1670
           in view and model definitions.
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1671
1672
           :param str groups: comma-separated list of fully-qualified group
1673
                              external IDs, e.g.: ``base.group_user,base.group_system``
1674
           :return: True if the current user is a member of one of the
1675
                    given groups
1676
        """
1677
        return any([self.pool.get('res.users').has_group(cr, uid, group_ext_id)
1678
                        for group_ext_id in groups.split(',')])
1679
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1680
    def __view_look_dom(self, cr, user, node, view_id, in_tree_view, model_fields, context=None):
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1681
        """Return the description of the fields in the node.
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1682
1683
        In a normal call to this method, node is a complete view architecture
1684
        but it is actually possible to give some sub-node (this is used so
1685
        that the method can call itself recursively).
1686
1687
        Originally, the field descriptions are drawn from the node itself.
1688
        But there is now some code calling fields_get() in order to merge some
1689
        of those information in the architecture.
1690
1691
        """
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
1692
        if context is None:
957 by Olivier Laurent
pep8
1693
            context = {}
922 by Christophe Simonis
convert tabs to 4 spaces
1694
        result = False
1695
        fields = {}
2148.1.2 by Numerigraphe - Lionel Sausin
[REF] 'children' instead of 'childs'
1696
        children = True
922 by Christophe Simonis
convert tabs to 4 spaces
1697
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1698
        modifiers = {}
1699
2017.2.52 by Ysa(Open ERP)
[IMP] diagram :- improve parser code(parse node and arrow field).
1700
        def encode(s):
1701
            if isinstance(s, unicode):
1702
                return s.encode('utf8')
1703
            return s
2111.9.1 by Olivier Dony
[FIX] reviewed and improved some naming and formatting, plus removed attrs from field if groups disallows showing it
1704
2111.5.20 by HDA(OpenERP)
bug with field with widget=selection + groups if user has no access right
1705
        def check_group(node):
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1706
            """Apply group restrictions,  may be set at view level or model level::
1707
               * at view level this means the element should be made invisible to
1708
                 people who are not members
1709
               * at model level (exclusively for fields, obviously), this means
1710
                 the field should be completely removed from the view, as it is
1711
                 completely unavailable for non-members
1712
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
1713
               :return: True if field should be included in the result of fields_view_get
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1714
            """
1715
            if node.tag == 'field' and node.get('name') in self._all_columns:
1716
                column = self._all_columns[node.get('name')].column
1717
                if column.groups and not self.user_has_groups(cr, user,
1718
                                                              groups=column.groups,
1719
                                                              context=context):
1720
                    node.getparent().remove(node)
1721
                    fields.pop(node.get('name'), None)
1722
                    # no point processing view-level ``groups`` anymore, return
1723
                    return False
2111.5.20 by HDA(OpenERP)
bug with field with widget=selection + groups if user has no access right
1724
            if node.get('groups'):
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1725
                can_see = self.user_has_groups(cr, user,
1726
                                               groups=node.get('groups'),
1727
                                               context=context)
2111.9.1 by Olivier Dony
[FIX] reviewed and improved some naming and formatting, plus removed attrs from field if groups disallows showing it
1728
                if not can_see:
2111.5.20 by HDA(OpenERP)
bug with field with widget=selection + groups if user has no access right
1729
                    node.set('invisible', '1')
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1730
                    modifiers['invisible'] = True
2111.9.1 by Olivier Dony
[FIX] reviewed and improved some naming and formatting, plus removed attrs from field if groups disallows showing it
1731
                    if 'attrs' in node.attrib:
1732
                        del(node.attrib['attrs']) #avoid making field visible later
2111.5.28 by Olivier Dony
[FIX] orm/selection widget: fixed leftover typos
1733
                del(node.attrib['groups'])
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1734
            return True
2111.9.1 by Olivier Dony
[FIX] reviewed and improved some naming and formatting, plus removed attrs from field if groups disallows showing it
1735
2017.2.46 by Ysa(Open ERP)
[IMP] diagram :- parse node and arrow tag field.
1736
        if node.tag in ('field', 'node', 'arrow'):
1737
            if node.get('object'):
1738
                attrs = {}
1739
                views = {}
2017.2.52 by Ysa(Open ERP)
[IMP] diagram :- improve parser code(parse node and arrow field).
1740
                xml = "<form>"
2017.2.46 by Ysa(Open ERP)
[IMP] diagram :- parse node and arrow tag field.
1741
                for f in node:
4682.2.23 by Raphael Collet
[FIX] orm: replace incorrect usage of 'in' by '=='
1742
                    if f.tag == 'field':
2017.2.52 by Ysa(Open ERP)
[IMP] diagram :- improve parser code(parse node and arrow field).
1743
                        xml += etree.tostring(f, encoding="utf-8")
1744
                xml += "</form>"
1745
                new_xml = etree.fromstring(encode(xml))
1746
                ctx = context.copy()
1747
                ctx['base_model_name'] = self._name
3055 by Olivier Dony
[MERGE+FIX] fixes for translations,sequence,copy,error messages - some patches courtesy of Margarita Manterola and Don Kirkby
1748
                xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
1749
                views['form'] = {
2017.2.52 by Ysa(Open ERP)
[IMP] diagram :- improve parser code(parse node and arrow field).
1750
                    'arch': xarch,
1751
                    'fields': xfields
1752
                }
1753
                attrs = {'views': views}
3055 by Olivier Dony
[MERGE+FIX] fixes for translations,sequence,copy,error messages - some patches courtesy of Margarita Manterola and Don Kirkby
1754
                fields = xfields
1845 by HDA(OpenERP)
[Merged]
1755
            if node.get('name'):
922 by Christophe Simonis
convert tabs to 4 spaces
1756
                attrs = {}
1757
                try:
1845 by HDA(OpenERP)
[Merged]
1758
                    if node.get('name') in self._columns:
1759
                        column = self._columns[node.get('name')]
922 by Christophe Simonis
convert tabs to 4 spaces
1760
                    else:
1845 by HDA(OpenERP)
[Merged]
1761
                        column = self._inherit_fields[node.get('name')][2]
3055 by Olivier Dony
[MERGE+FIX] fixes for translations,sequence,copy,error messages - some patches courtesy of Margarita Manterola and Don Kirkby
1762
                except Exception:
1766.2.5 by Christophe Simonis
[FIX] allow to use server-side domains with widget='selection' fields
1763
                    column = False
922 by Christophe Simonis
convert tabs to 4 spaces
1764
1766.2.5 by Christophe Simonis
[FIX] allow to use server-side domains with widget='selection' fields
1765
                if column:
2240 by Christophe Simonis
[FIX]orm: invisible fields with widget=selection must have at least the default value into the selection
1766
                    relation = self.pool.get(column._obj)
1767
2148.1.2 by Numerigraphe - Lionel Sausin
[REF] 'children' instead of 'childs'
1768
                    children = False
922 by Christophe Simonis
convert tabs to 4 spaces
1769
                    views = {}
1845 by HDA(OpenERP)
[Merged]
1770
                    for f in node:
4110.1.9 by Antony Lesuisse
embbded kanban view
1771
                        if f.tag in ('form', 'tree', 'graph', 'kanban'):
1845 by HDA(OpenERP)
[Merged]
1772
                            node.remove(f)
1321 by Christophe Simonis
[FIX] translation of some labels in one2many fields. Thanks to Dainius Malachovskis for the base patch.
1773
                            ctx = context.copy()
1774
                            ctx['base_model_name'] = self._name
2240 by Christophe Simonis
[FIX]orm: invisible fields with widget=selection must have at least the default value into the selection
1775
                            xarch, xfields = relation.__view_look_dom_arch(cr, user, f, view_id, ctx)
1845 by HDA(OpenERP)
[Merged]
1776
                            views[str(f.tag)] = {
922 by Christophe Simonis
convert tabs to 4 spaces
1777
                                'arch': xarch,
1778
                                'fields': xfields
1779
                            }
1780
                    attrs = {'views': views}
1845 by HDA(OpenERP)
[Merged]
1781
                    if node.get('widget') and node.get('widget') == 'selection':
2670.1.4 by Olivier Dony
[IMP] orm.fields_view_get: return cached selection values even when field is not invisible
1782
                        # Prepare the cached selection list for the client. This needs to be
1783
                        # done even when the field is invisible to the current user, because
1784
                        # other events could need to change its value to any of the selectable ones
1785
                        # (such as on_change events, refreshes, etc.)
1786
1787
                        # If domain and context are strings, we keep them for client-side, otherwise
1788
                        # we evaluate them server-side to consider them when generating the list of
1789
                        # possible values
1790
                        # TODO: find a way to remove this hack, by allow dynamic domains
1791
                        dom = []
1792
                        if column._domain and not isinstance(column._domain, basestring):
4573.2.1 by Stefan Rijnhart
[FIX] Append view domain (in case of selection widget) to a copy of the field's domain
1793
                            dom = list(column._domain)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
1794
                        dom += eval(node.get('domain', '[]'), {'uid': user, 'time': time})
2670.1.4 by Olivier Dony
[IMP] orm.fields_view_get: return cached selection values even when field is not invisible
1795
                        search_context = dict(context)
1796
                        if column._context and not isinstance(column._context, basestring):
1797
                            search_context.update(column._context)
2675 by Olivier Dony
[FIX] orm: fields_view_get selection values must properly heed ir.rules:
1798
                        attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1)
2670.1.4 by Olivier Dony
[IMP] orm.fields_view_get: return cached selection values even when field is not invisible
1799
                        if (node.get('required') and not int(node.get('required'))) or not column.required:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
1800
                            attrs['selection'].append((False, ''))
1845 by HDA(OpenERP)
[Merged]
1801
                fields[node.get('name')] = attrs
922 by Christophe Simonis
convert tabs to 4 spaces
1802
3523.1.1 by gpa
[FIXED] Fixed the problem keyerror id
1803
                field = model_fields.get(node.get('name'))
1804
                if field:
1805
                    transfer_field_to_modifiers(field, modifiers)
4484 by niv-openerp
[REVERT] removed server-side evaluation of field options
1806
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1807
1845 by HDA(OpenERP)
[Merged]
1808
        elif node.tag in ('form', 'tree'):
1809
            result = self.view_header_get(cr, user, False, node.tag, context)
922 by Christophe Simonis
convert tabs to 4 spaces
1810
            if result:
1845 by HDA(OpenERP)
[Merged]
1811
                node.set('string', result)
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1812
            in_tree_view = node.tag == 'tree'
1236 by Christophe Simonis
fields_view_get return also the fields specified in the attributes of the <calendar> tag
1813
1845 by HDA(OpenERP)
[Merged]
1814
        elif node.tag == 'calendar':
1236 by Christophe Simonis
fields_view_get return also the fields specified in the attributes of the <calendar> tag
1815
            for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'):
1845 by HDA(OpenERP)
[Merged]
1816
                if node.get(additional_field):
1817
                    fields[node.get(additional_field)] = {}
1236 by Christophe Simonis
fields_view_get return also the fields specified in the attributes of the <calendar> tag
1818
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
1819
        if not check_group(node):
1820
            # node must be removed, no need to proceed further with its children
1821
            return fields
922 by Christophe Simonis
convert tabs to 4 spaces
1822
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1823
        # The view architeture overrides the python model.
1824
        # Get the attrs before they are (possibly) deleted by check_group below
1825
        transfer_node_to_modifiers(node, modifiers, context, in_tree_view)
1826
1827
        # TODO remove attrs couterpart in modifiers when invisible is true ?
1828
1845 by HDA(OpenERP)
[Merged]
1829
        # translate view
3466.1.5 by Vo Minh Thu
[FIX] orm/fields_view_get: translate correctly confirm/sum/help:
1830
        if 'lang' in context:
4216 by Fabien Pinckaers
[IMP] Translations of static terms in views
1831
            if node.text and node.text.strip():
1832
                trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.text.strip())
1833
                if trans:
4223 by Fabien Pinckaers
[FIX] whitespace lost in translation
1834
                    node.text = node.text.replace(node.text.strip(), trans)
4216 by Fabien Pinckaers
[IMP] Translations of static terms in views
1835
            if node.tail and node.tail.strip():
1836
                trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.tail.strip())
1837
                if trans:
4223 by Fabien Pinckaers
[FIX] whitespace lost in translation
1838
                    node.tail =  node.tail.replace(node.tail.strip(), trans)
4216 by Fabien Pinckaers
[IMP] Translations of static terms in views
1839
3466.1.5 by Vo Minh Thu
[FIX] orm/fields_view_get: translate correctly confirm/sum/help:
1840
            if node.get('string') and not result:
2713 by Raphael Valyi
[FIX] orm: properly handle unicode values returned by ir.translation
1841
                trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('string'))
3110.1.1 by OpenERP
[FIX] orm: fixed translations for inherited views when the parent view uses a different model.
1842
                if trans == node.get('string') and ('base_model_name' in context):
3118 by Vo Minh Thu (OpenERP)
[MERGE] orm: fixed translations for inherited views when the parent view uses a different model
1843
                    # If translation is same as source, perhaps we'd have more luck with the alternative model name
1844
                    # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model
2713 by Raphael Valyi
[FIX] orm: properly handle unicode values returned by ir.translation
1845
                    trans = self.pool.get('ir.translation')._get_source(cr, user, context['base_model_name'], 'view', context['lang'], node.get('string'))
1845 by HDA(OpenERP)
[Merged]
1846
                if trans:
1847
                    node.set('string', trans)
4219 by Xavier Morel
[IMP] massage all translatable attributes in a loop instead of copy/pasting a billion versions
1848
4594.1.4 by Olivier Dony
[MERGE] Forward-port 6.1 bugfixes up to rev. 4307
1849
            for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'):
4222 by Raphael Collet
[IMP] orm translations: improve variable names
1850
                attr_value = node.get(attr_name)
1851
                if attr_value:
1852
                    trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], attr_value)
1853
                    if trans:
1854
                        node.set(attr_name, trans)
922 by Christophe Simonis
convert tabs to 4 spaces
1855
2823 by nch at tinyerp
[FIX]:search_view 'groups=' attribute in the child of a field tag was never evaluted:
1856
        for f in node:
2148.1.3 by Numerigraphe - Lionel Sausin
[MERGE] merge from 6.0.0-RC1
1857
            if children or (node.tag == 'field' and f.tag in ('filter','separator')):
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1858
                fields.update(self.__view_look_dom(cr, user, f, view_id, in_tree_view, model_fields, context))
1341.1.2 by Christophe Simonis
[IMP] views don't need to include the state field anymore to be state aware
1859
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1860
        transfer_modifiers_to_node(modifiers, node)
922 by Christophe Simonis
convert tabs to 4 spaces
1861
        return fields
1862
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1863
    def _disable_workflow_buttons(self, cr, user, node):
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
1864
        """ Set the buttons in node to readonly if the user can't activate them. """
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1865
        if user == 1:
1866
            # admin user can always activate workflow buttons
1867
            return node
922 by Christophe Simonis
convert tabs to 4 spaces
1868
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1869
        # TODO handle the case of more than one workflow for a model or multiple
1870
        # transitions with different groups and same signal
1548 by Christophe Simonis
[IMP] use the pooler directly instead of the 'object_proxy'
1871
        usersobj = self.pool.get('res.users')
1845 by HDA(OpenERP)
[Merged]
1872
        buttons = (n for n in node.getiterator('button') if n.get('type') != 'object')
1608 by Stephane Wirtel
[REF] Use the xpath with condition
1873
        for button in buttons:
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1874
            user_groups = usersobj.read(cr, user, [user], ['groups_id'])[0]['groups_id']
1875
            cr.execute("""SELECT DISTINCT t.group_id
1876
                        FROM wkf
1877
                  INNER JOIN wkf_activity a ON a.wkf_id = wkf.id
1878
                  INNER JOIN wkf_transition t ON (t.act_to = a.id)
1879
                       WHERE wkf.osv = %s
1880
                         AND t.signal = %s
2882 by Olivier Dony
[FIX] orm, workflow: do not disable wkf buttons unless explicit group is set on transitions
1881
                         AND t.group_id is NOT NULL
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1882
                   """, (self._name, button.get('name')))
2882 by Olivier Dony
[FIX] orm, workflow: do not disable wkf buttons unless explicit group is set on transitions
1883
            group_ids = [x[0] for x in cr.fetchall() if x[0]]
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1884
            can_click = not group_ids or bool(set(user_groups).intersection(group_ids))
1857 by HDA(OpenERP)
etree conversion changes
1885
            button.set('readonly', str(int(not can_click)))
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1886
        return node
922 by Christophe Simonis
convert tabs to 4 spaces
1887
2870 by Olivier Dony
[REM] base, workflow: removed res.roles object, replaced by dedicated res.groups
1888
    def __view_look_dom_arch(self, cr, user, node, view_id, context=None):
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1889
        """ Return an architecture and a description of all the fields.
1890
1891
        The field description combines the result of fields_get() and
1892
        __view_look_dom().
1893
1894
        :param node: the architecture as as an etree
1895
        :return: a tuple (arch, fields) where arch is the given node as a
1896
            string and fields is the description of all the fields.
1897
1898
        """
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
1899
        fields = {}
1900
        if node.tag == 'diagram':
1901
            if node.getchildren()[0].tag == 'node':
4344.1.43 by Olivier Dony
[MERGE] sync with trunk
1902
                node_model = self.pool.get(node.getchildren()[0].get('object'))
1903
                node_fields = node_model.fields_get(cr, user, None, context)
3466.1.4 by Vo Minh Thu
[REF] orm/fields_view_get: replaced simple loop by dict.update().
1904
                fields.update(node_fields)
4344.1.43 by Olivier Dony
[MERGE] sync with trunk
1905
                if not node.get("create") and not node_model.check_access_rights(cr, user, 'create', raise_exception=False):
1906
                    node.set("create", 'false')
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
1907
            if node.getchildren()[1].tag == 'arrow':
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1908
                arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, None, context)
3466.1.4 by Vo Minh Thu
[REF] orm/fields_view_get: replaced simple loop by dict.update().
1909
                fields.update(arrow_fields)
2017.2.52 by Ysa(Open ERP)
[IMP] diagram :- improve parser code(parse node and arrow field).
1910
        else:
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1911
            fields = self.fields_get(cr, user, None, context)
1912
        fields_def = self.__view_look_dom(cr, user, node, view_id, False, fields, context=context)
1913
        node = self._disable_workflow_buttons(cr, user, node)
4318.2.4 by Jigar Amin - OpenERP
[REF/IMP] refectorec the varibale names and added the gantt view, added create ndoe button diable condition
1914
        if node.tag in ('kanban', 'tree', 'form', 'gantt'):
4344.1.43 by Olivier Dony
[MERGE] sync with trunk
1915
            for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')):
1916
                if not node.get(action) and not self.check_access_rights(cr, user, operation, raise_exception=False):
4318.2.4 by Jigar Amin - OpenERP
[REF/IMP] refectorec the varibale names and added the gantt view, added create ndoe button diable condition
1917
                    node.set(action, 'false')
3497 by Vo Minh Thu
[REVERT] re-apply the 4 commits that were reverted just after, 4 commits ago.
1918
        arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
1919
        for k in fields.keys():
1920
            if k not in fields_def:
1921
                del fields[k]
922 by Christophe Simonis
convert tabs to 4 spaces
1922
        for field in fields_def:
1760.1.4 by Christophe Simonis
[FIX] allow to use the field 'id' in views (i.e., needed for domain)
1923
            if field == 'id':
2148.1.1 by Numerigraphe - Lionel Sausin
[IMP] wording: "customize" intead of "customise"
1924
                # sometime, the view may contain the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
1760.1.4 by Christophe Simonis
[FIX] allow to use the field 'id' in views (i.e., needed for domain)
1925
                fields['id'] = {'readonly': True, 'type': 'integer', 'string': 'ID'}
1926
            elif field in fields:
1119.1.68 by P. Christeas
[Under-the-carpet-FIX] Silently ignore missing fields in views.
1927
                fields[field].update(fields_def[field])
1341.6.1 by Raphaël Valyi
view debbugging improvement: cf branche definition
1928
            else:
1602 by Christophe Simonis
[MERGE] merge smile branch.
1929
                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))
1930
                res = cr.fetchall()[:]
1931
                model = res[0][1]
1932
                res.insert(0, ("Can't find field '%s' in the following view parts composing the view of object model '%s':" % (field, model), None))
1933
                msg = "\n * ".join([r[0] for r in res])
2148.1.1 by Numerigraphe - Lionel Sausin
[IMP] wording: "customize" intead of "customise"
1934
                msg += "\n\nEither you wrongly customized this view, or some modules bringing those views are not compatible with your current data model"
3976.1.16 by Vo Minh Thu
[IMP] openerp.loglevels: removed calls to openerp.loglevels.Logger().
1935
                _logger.error(msg)
1602 by Christophe Simonis
[MERGE] merge smile branch.
1936
                raise except_orm('View error', msg)
922 by Christophe Simonis
convert tabs to 4 spaces
1937
        return arch, fields
1938
3594.1.3 by Xavier Morel
[IMP] regroup all __get_default_$name_view calls into a single parametric getattr
1939
    def _get_default_form_view(self, cr, user, context=None):
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
1940
        """ Generates a default single-line form view using all fields
1941
        of the current model except the m2m and o2m ones.
1942
1943
        :param cr: database cursor
1944
        :param int user: user id
1945
        :param dict context: connection context
1946
        :returns: a form view as an lxml document
1947
        :rtype: etree._Element
1948
        """
1949
        view = etree.Element('form', string=self._description)
3594.1.2 by Xavier Morel
[REF] extract the two inlined default view generators left into fields_view_get into their own methods
1950
        # TODO it seems fields_get can be replaced by _all_columns (no need for translation)
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
1951
        for field, descriptor in self.fields_get(cr, user, context=context).iteritems():
1952
            if descriptor['type'] in ('one2many', 'many2many'):
1953
                continue
1954
            etree.SubElement(view, 'field', name=field)
1955
            if descriptor['type'] == 'text':
1956
                etree.SubElement(view, 'newline')
1957
        return view
3594.1.2 by Xavier Morel
[REF] extract the two inlined default view generators left into fields_view_get into their own methods
1958
4185.1.6 by Fabien Pinckaers
[IMP] removing select=? and adding version=7
1959
    def _get_default_search_view(self, cr, user, context=None):
4272.1.1 by Antony Lesuisse
[IMP] rec_name assertion and fallback
1960
        """ Generates a single-field search view, based on _rec_name.
4185.1.6 by Fabien Pinckaers
[IMP] removing select=? and adding version=7
1961
1962
        :param cr: database cursor
1963
        :param int user: user id
1964
        :param dict context: connection context
1965
        :returns: a tree view as an lxml document
1966
        :rtype: etree._Element
1967
        """
1968
        view = etree.Element('search', string=self._description)
4272.1.7 by Vo Minh Thu
[MEOW] lolcat crazy ninja fix in the branch, take two!
1969
        etree.SubElement(view, 'field', name=self._rec_name_fallback(cr, user, context))
4185.1.6 by Fabien Pinckaers
[IMP] removing select=? and adding version=7
1970
        return view
1971
3594.1.3 by Xavier Morel
[IMP] regroup all __get_default_$name_view calls into a single parametric getattr
1972
    def _get_default_tree_view(self, cr, user, context=None):
4272.1.1 by Antony Lesuisse
[IMP] rec_name assertion and fallback
1973
        """ Generates a single-field tree view, based on _rec_name.
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
1974
1975
        :param cr: database cursor
1976
        :param int user: user id
1977
        :param dict context: connection context
1978
        :returns: a tree view as an lxml document
1979
        :rtype: etree._Element
1980
        """
1981
        view = etree.Element('tree', string=self._description)
4272.1.7 by Vo Minh Thu
[MEOW] lolcat crazy ninja fix in the branch, take two!
1982
        etree.SubElement(view, 'field', name=self._rec_name_fallback(cr, user, context))
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
1983
        return view
3594.1.2 by Xavier Morel
[REF] extract the two inlined default view generators left into fields_view_get into their own methods
1984
3594.1.3 by Xavier Morel
[IMP] regroup all __get_default_$name_view calls into a single parametric getattr
1985
    def _get_default_calendar_view(self, cr, user, context=None):
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
1986
        """ Generates a default calendar view by trying to infer
1987
        calendar fields from a number of pre-set attribute names
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
1988
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
1989
        :param cr: database cursor
1990
        :param int user: user id
1991
        :param dict context: connection context
1992
        :returns: a calendar view
1993
        :rtype: etree._Element
922 by Christophe Simonis
convert tabs to 4 spaces
1994
        """
3594.1.7 by Xavier Morel
[ADD] small helper to calendar generation code, in order to make flow clearer
1995
        def set_first_of(seq, in_, to):
3717 by Vo Minh Thu
[MERGE] orm: use directly lxml.etree to generate default views.
1996
            """Sets the first value of ``seq`` also found in ``in_`` to
3594.1.7 by Xavier Morel
[ADD] small helper to calendar generation code, in order to make flow clearer
1997
            the ``to`` attribute of the view being closed over.
1998
1999
            Returns whether it's found a suitable value (and set it on
2000
            the attribute) or not
2001
            """
2002
            for item in seq:
2003
                if item in in_:
3717 by Vo Minh Thu
[MERGE] orm: use directly lxml.etree to generate default views.
2004
                    view.set(to, item)
3594.1.7 by Xavier Morel
[ADD] small helper to calendar generation code, in order to make flow clearer
2005
                    return True
2006
            return False
2007
3594.1.5 by Xavier Morel
[IMP] convert default calendar view generation (from fields_view_get) to lxml.etree
2008
        view = etree.Element('calendar', string=self._description)
4272.1.7 by Vo Minh Thu
[MEOW] lolcat crazy ninja fix in the branch, take two!
2009
        etree.SubElement(view, 'field', self._rec_name_fallback(cr, user, context))
1845 by HDA(OpenERP)
[Merged]
2010
4682.2.9 by Xavier Morel
[REM] unnecessary parens
2011
        if self._date_name not in self._columns:
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
2012
            date_found = False
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2013
            for dt in ['date', 'date_start', 'x_date', 'x_date_start']:
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
2014
                if dt in self._columns:
2015
                    self._date_name = dt
2016
                    date_found = True
2017
                    break
1845 by HDA(OpenERP)
[Merged]
2018
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
2019
            if not date_found:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2020
                raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!"))
3594.1.5 by Xavier Morel
[IMP] convert default calendar view generation (from fields_view_get) to lxml.etree
2021
        view.set('date_start', self._date_name)
1845 by HDA(OpenERP)
[Merged]
2022
3594.1.7 by Xavier Morel
[ADD] small helper to calendar generation code, in order to make flow clearer
2023
        set_first_of(["user_id", "partner_id", "x_user_id", "x_partner_id"],
2024
                     self._columns, 'color')
1845 by HDA(OpenERP)
[Merged]
2025
3594.1.7 by Xavier Morel
[ADD] small helper to calendar generation code, in order to make flow clearer
2026
        if not set_first_of(["date_stop", "date_end", "x_date_stop", "x_date_end"],
2027
                            self._columns, 'date_stop'):
2028
            if not set_first_of(["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"],
2029
                                self._columns, 'date_delay'):
3594.1.6 by Xavier Morel
[ADD] error when neither date_stop nor date_delay can be generated for a default calendar view
2030
                raise except_orm(
2031
                    _('Invalid Object Architecture!'),
4682.2.9 by Xavier Morel
[REM] unnecessary parens
2032
                    _("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay" % self._name))
922 by Christophe Simonis
convert tabs to 4 spaces
2033
3594.1.5 by Xavier Morel
[IMP] convert default calendar view generation (from fields_view_get) to lxml.etree
2034
        return view
922 by Christophe Simonis
convert tabs to 4 spaces
2035
2036
    #
2037
    # if view_id, view_type is not required
2038
    #
1845 by HDA(OpenERP)
[Merged]
2039
    def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2040
        """
2041
        Get the detailed composition of the requested view like fields, model, view architecture
2042
2043
        :param cr: database cursor
2044
        :param user: current user id
2045
        :param view_id: id of the view or None
2046
        :param view_type: type of the view to return if view_id is None ('form', tree', ...)
2047
        :param context: context arguments, like lang, time zone
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
2048
        :param toolbar: true to include contextual actions
3538 by Antony Lesuisse
[FIX] get rid of unsued menus and submenu
2049
        :param submenu: deprecated
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
2050
        :return: dictionary describing the composition of the requested view (including inherited views and extensions)
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2051
        :raise AttributeError:
2052
                            * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
2053
                            * if some tag other than 'position' is found in parent view
2054
        :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
2055
2056
        """
3466 by Vo Minh Thu
[REF] orm: fields_get in pieces, now with docstring.
2057
        if context is None:
957 by Olivier Laurent
pep8
2058
            context = {}
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
2059
1404.2.11 by Christophe Simonis
[FIX] handle the case when xml are in unicode
2060
        def encode(s):
2061
            if isinstance(s, unicode):
2062
                return s.encode('utf8')
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
2063
            return s
1404.2.11 by Christophe Simonis
[FIX] handle the case when xml are in unicode
2064
3089 by YSA (OpenERP), Olivier Dony (OpenERP)
[IMP] orm: somewhat better error message when view inheritance fails on non-<field> element
2065
        def raise_view_error(error_msg, child_view_id):
2066
            view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2067
            error_msg = error_msg % {'parent_xml_id': view.xml_id}
3466 by Vo Minh Thu
[REF] orm: fields_get in pieces, now with docstring.
2068
            raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
2069
                                 %  (child_view.xml_id, self._name, error_msg))
3089 by YSA (OpenERP), Olivier Dony (OpenERP)
[IMP] orm: somewhat better error message when view inheritance fails on non-<field> element
2070
3466.1.1 by Vo Minh Thu
[REF] orm: simpler `_find` function, renamed `locate`.
2071
        def locate(source, spec):
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2072
            """ Locate a node in a source (parent) architecture.
3466.1.1 by Vo Minh Thu
[REF] orm: simpler `_find` function, renamed `locate`.
2073
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2074
            Given a complete source (parent) architecture (i.e. the field
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2075
            `arch` in a view), and a 'spec' node (a node in an inheriting
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2076
            view that specifies the location in the source view of what
2077
            should be changed), return (if it exists) the node in the
2078
            source view matching the specification.
3466.1.1 by Vo Minh Thu
[REF] orm: simpler `_find` function, renamed `locate`.
2079
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2080
            :param source: a parent architecture to modify
2081
            :param spec: a modifying node in an inheriting view
2082
            :return: a node in the source matching the spec
2083
3466.1.1 by Vo Minh Thu
[REF] orm: simpler `_find` function, renamed `locate`.
2084
            """
2085
            if spec.tag == 'xpath':
2086
                nodes = source.xpath(spec.get('expr'))
2087
                return nodes[0] if nodes else None
2088
            elif spec.tag == 'field':
2089
                # Only compare the field name: a field can be only once in a given view
2090
                # at a given level (and for multilevel expressions, we should use xpath
2091
                # inheritance spec anyway).
2092
                for node in source.getiterator('field'):
2093
                    if node.get('name') == spec.get('name'):
2094
                        return node
2095
                return None
4194.1.2 by Olivier Dony
[IMP] orm: improved @version spec validation for inherited views
2096
2097
            for node in source.getiterator(spec.tag):
2098
                if isinstance(node, SKIPPED_ELEMENT_TYPES):
2099
                    continue
2100
                if all(node.get(attr) == spec.get(attr) \
2101
                        for attr in spec.attrib
2102
                            if attr not in ('position','version')):
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2103
                    # Version spec should match parent's root element's version
4194.1.2 by Olivier Dony
[IMP] orm: improved @version spec validation for inherited views
2104
                    if spec.get('version') and spec.get('version') != source.get('version'):
2105
                        return None
2106
                    return node
2107
            return None
3466.1.1 by Vo Minh Thu
[REF] orm: simpler `_find` function, renamed `locate`.
2108
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2109
        def apply_inheritance_specs(source, specs_arch, inherit_id=None):
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2110
            """ Apply an inheriting view.
2111
2112
            Apply to a source architecture all the spec nodes (i.e. nodes
2113
            describing where and what changes to apply to some parent
2114
            architecture) given by an inheriting view.
2115
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2116
            :param source: a parent architecture to modify
2117
            :param specs_arch: a modifying architecture in an inheriting view
2118
            :param inherit_id: the database id of the inheriting view
2119
            :return: a modified source where the specs are applied
2120
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2121
            """
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2122
            specs_tree = etree.fromstring(encode(specs_arch))
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2123
            # Queue of specification nodes (i.e. nodes describing where and
2124
            # changes to apply to some parent architecture).
2125
            specs = [specs_tree]
2126
2127
            while len(specs):
2128
                spec = specs.pop(0)
2129
                if isinstance(spec, SKIPPED_ELEMENT_TYPES):
2130
                    continue
2131
                if spec.tag == 'data':
2132
                    specs += [ c for c in specs_tree ]
2133
                    continue
2134
                node = locate(source, spec)
1845 by HDA(OpenERP)
[Merged]
2135
                if node is not None:
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2136
                    pos = spec.get('position', 'inside')
957 by Olivier Laurent
pep8
2137
                    if pos == 'replace':
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2138
                        if node.getparent() is None:
2139
                            source = copy.deepcopy(spec[0])
1861 by PSO(OpenERP)
Changed licencing
2140
                        else:
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2141
                            for child in spec:
1861 by PSO(OpenERP)
Changed licencing
2142
                                node.addprevious(child)
2143
                            node.getparent().remove(node)
1887 by Stephane Wirtel
[IMP] Add the support for the override of any attribute.
2144
                    elif pos == 'attributes':
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2145
                        for child in spec.getiterator('attribute'):
1887 by Stephane Wirtel
[IMP] Add the support for the override of any attribute.
2146
                            attribute = (child.get('name'), child.text and child.text.encode('utf8') or None)
2147
                            if attribute[1]:
2148
                                node.set(attribute[0], attribute[1])
2149
                            else:
2150
                                del(node.attrib[attribute[0]])
922 by Christophe Simonis
convert tabs to 4 spaces
2151
                    else:
1845 by HDA(OpenERP)
[Merged]
2152
                        sib = node.getnext()
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2153
                        for child in spec:
1845 by HDA(OpenERP)
[Merged]
2154
                            if pos == 'inside':
2155
                                node.append(child)
2156
                            elif pos == 'after':
2157
                                if sib is None:
2158
                                    node.addnext(child)
3097 by Olivier Dony
[FIX] orm: preserve elements order when inheriting views, also after a leaf element
2159
                                    node = child
922 by Christophe Simonis
convert tabs to 4 spaces
2160
                                else:
1845 by HDA(OpenERP)
[Merged]
2161
                                    sib.addprevious(child)
2162
                            elif pos == 'before':
2163
                                node.addprevious(child)
2164
                            else:
3089 by YSA (OpenERP), Olivier Dony (OpenERP)
[IMP] orm: somewhat better error message when view inheritance fails on non-<field> element
2165
                                raise_view_error("Invalid position value: '%s'" % pos, inherit_id)
922 by Christophe Simonis
convert tabs to 4 spaces
2166
                else:
2167
                    attrs = ''.join([
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2168
                        ' %s="%s"' % (attr, spec.get(attr))
2169
                        for attr in spec.attrib
922 by Christophe Simonis
convert tabs to 4 spaces
2170
                        if attr != 'position'
2171
                    ])
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2172
                    tag = "<%s%s>" % (spec.tag, attrs)
4194.1.2 by Olivier Dony
[IMP] orm: improved @version spec validation for inherited views
2173
                    if spec.get('version') and spec.get('version') != source.get('version'):
2174
                        raise_view_error("Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
2175
                                            (tag, spec.get('version'), source.get('version')), inherit_id)
3089 by YSA (OpenERP), Olivier Dony (OpenERP)
[IMP] orm: somewhat better error message when view inheritance fails on non-<field> element
2176
                    raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2177
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2178
            return source
2179
3611.1.1 by sebastien beau
[REF] refactor code in order to add the possibility to overwrite the way to inherit the view
2180
        def apply_view_inheritance(cr, user, source, inherit_id):
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2181
            """ Apply all the (directly and indirectly) inheriting views.
2182
2183
            :param source: a parent architecture to modify (with parent
2184
                modifications already applied)
3617 by Olivier Dony
[MERGE] refactoring: view inheritance query moved to ir.ui.view, courtesy of Sebastien Beau (Akretion)
2185
            :param inherit_id: the database view_id of the parent view
3466.1.3 by Vo Minh Thu
[FIX] orm: typo in fields_view_get.
2186
            :return: a modified source where all the modifying architecture
2187
                are applied
2188
2189
            """
3617 by Olivier Dony
[MERGE] refactoring: view inheritance query moved to ir.ui.view, courtesy of Sebastien Beau (Akretion)
2190
            sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name)
2191
            for (view_arch, view_id) in sql_inherit:
2192
                source = apply_inheritance_specs(source, view_arch, view_id)
2193
                source = apply_view_inheritance(cr, user, source, view_id)
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2194
            return source
922 by Christophe Simonis
convert tabs to 4 spaces
2195
957 by Olivier Laurent
pep8
2196
        result = {'type': view_type, 'model': self._name}
922 by Christophe Simonis
convert tabs to 4 spaces
2197
2198
        sql_res = False
3110.1.1 by OpenERP
[FIX] orm: fixed translations for inherited views when the parent view uses a different model.
2199
        parent_view_model = None
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2200
        view_ref = context.get(view_type + '_view_ref')
2201
        # Search for a root (i.e. without any parent) view.
2202
        while True:
2536 by nch at tinyerp
[FIX]:*_view_ref should execute only if there is no view_id provided
2203
            if view_ref and not view_id:
1844.1.31 by Stephane Wirtel
[MERGE] Backport: http://bazaar.launchpad.net/~openobject-training/+junk/server/revision/1822
2204
                if '.' in view_ref:
2205
                    module, view_ref = view_ref.split('.', 1)
2206
                    cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref))
2207
                    view_ref_res = cr.fetchone()
2208
                    if view_ref_res:
2209
                        view_id = view_ref_res[0]
2210
922 by Christophe Simonis
convert tabs to 4 spaces
2211
            if view_id:
3358 by Olivier Dony
[IMP] orm.fields_view_get: allow passing a view_id from a different model for creative inheritance
2212
                cr.execute("""SELECT arch,name,field_parent,id,type,inherit_id,model
2213
                              FROM ir_ui_view
2214
                              WHERE id=%s""", (view_id,))
922 by Christophe Simonis
convert tabs to 4 spaces
2215
            else:
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2216
                cr.execute("""SELECT arch,name,field_parent,id,type,inherit_id,model
2217
                              FROM ir_ui_view
2218
                              WHERE model=%s AND type=%s AND inherit_id IS NULL
2219
                              ORDER BY priority""", (self._name, view_type))
2220
            sql_res = cr.dictfetchone()
1930 by ame (Tiny)
[IMP] improved `fields_view_get` (auto create search view)
2221
922 by Christophe Simonis
convert tabs to 4 spaces
2222
            if not sql_res:
2223
                break
1869.1.24 by nch at tinyerp
[MERGE]:from parent
2224
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2225
            view_id = sql_res['inherit_id'] or sql_res['id']
2226
            parent_view_model = sql_res['model']
2227
            if not sql_res['inherit_id']:
2228
                break
922 by Christophe Simonis
convert tabs to 4 spaces
2229
2230
        # if a view was found
2231
        if sql_res:
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2232
            source = etree.fromstring(encode(sql_res['arch']))
3594.1.1 by Xavier Morel
[IMP] bulk-update fields_view_get result via dict.update instead of a bunch of __setitem__
2233
            result.update(
3594.1.12 by Xavier Morel
[MERGE] trunk
2234
                arch=apply_view_inheritance(cr, user, source, sql_res['id']),
3594.1.1 by Xavier Morel
[IMP] bulk-update fields_view_get result via dict.update instead of a bunch of __setitem__
2235
                type=sql_res['type'],
2236
                view_id=sql_res['id'],
2237
                name=sql_res['name'],
2238
                field_parent=sql_res['field_parent'] or False)
922 by Christophe Simonis
convert tabs to 4 spaces
2239
        else:
2240
            # otherwise, build some kind of default view
3594.1.3 by Xavier Morel
[IMP] regroup all __get_default_$name_view calls into a single parametric getattr
2241
            try:
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
2242
                view = getattr(self, '_get_default_%s_view' % view_type)(
3594.1.3 by Xavier Morel
[IMP] regroup all __get_default_$name_view calls into a single parametric getattr
2243
                    cr, user, context)
2244
            except AttributeError:
3466.1.2 by Vo Minh Thu
[REF] fields_viw_get: trying to get something clearer...
2245
                # what happens here, graph case?
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2246
                raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
3594.1.3 by Xavier Morel
[IMP] regroup all __get_default_$name_view calls into a single parametric getattr
2247
3594.1.1 by Xavier Morel
[IMP] bulk-update fields_view_get result via dict.update instead of a bunch of __setitem__
2248
            result.update(
3594.1.4 by Xavier Morel
[IMP] etree-ify default view generations
2249
                arch=view,
3594.1.1 by Xavier Morel
[IMP] bulk-update fields_view_get result via dict.update instead of a bunch of __setitem__
2250
                name='default',
2251
                field_parent=False,
2252
                view_id=0)
922 by Christophe Simonis
convert tabs to 4 spaces
2253
3110.1.1 by OpenERP
[FIX] orm: fixed translations for inherited views when the parent view uses a different model.
2254
        if parent_view_model != self._name:
3118 by Vo Minh Thu (OpenERP)
[MERGE] orm: fixed translations for inherited views when the parent view uses a different model
2255
            ctx = context.copy()
2256
            ctx['base_model_name'] = parent_view_model
3110.1.1 by OpenERP
[FIX] orm: fixed translations for inherited views when the parent view uses a different model.
2257
        else:
3118 by Vo Minh Thu (OpenERP)
[MERGE] orm: fixed translations for inherited views when the parent view uses a different model
2258
            ctx = context
3110.1.1 by OpenERP
[FIX] orm: fixed translations for inherited views when the parent view uses a different model.
2259
        xarch, xfields = self.__view_look_dom_arch(cr, user, result['arch'], view_id, context=ctx)
922 by Christophe Simonis
convert tabs to 4 spaces
2260
        result['arch'] = xarch
2261
        result['fields'] = xfields
1845 by HDA(OpenERP)
[Merged]
2262
922 by Christophe Simonis
convert tabs to 4 spaces
2263
        if toolbar:
2264
            def clean(x):
2265
                x = x[2]
2266
                for key in ('report_sxw_content', 'report_rml_content',
2267
                        'report_sxw', 'report_rml',
2268
                        'report_sxw_content_data', 'report_rml_content_data'):
2269
                    if key in x:
2270
                        del x[key]
2271
                return x
2272
            ir_values_obj = self.pool.get('ir.values')
2273
            resprint = ir_values_obj.get(cr, user, 'action',
2274
                    'client_print_multi', [(self._name, False)], False,
2275
                    context)
2276
            resaction = ir_values_obj.get(cr, user, 'action',
2277
                    'client_action_multi', [(self._name, False)], False,
2278
                    context)
2279
2280
            resrelate = ir_values_obj.get(cr, user, 'action',
2281
                    'client_action_relate', [(self._name, False)], False,
2282
                    context)
3594.1.13 by Xavier Morel
[FIX] typo syntax error
2283
            resaction = [clean(action) for action in resaction
2284
                         if view_type == 'tree' or not action[2].get('multi')]
2285
            resprint = [clean(print_) for print_ in resprint
2286
                        if view_type == 'tree' or not print_[2].get('multi')]
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
2287
            #When multi="True" set it will display only in More of the list view
4495.3.1 by Mayur Maheshwari(OpenERP)
[IMP]orm : improve multi tag when multi=true action display in tree and when flase action display in form
2288
            resrelate = [clean(action) for action in resrelate
2289
                         if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')]
922 by Christophe Simonis
convert tabs to 4 spaces
2290
3594.1.11 by Xavier Morel
[IMP] fields_view_get: listcomps are still awesome
2291
            for x in itertools.chain(resprint, resaction, resrelate):
922 by Christophe Simonis
convert tabs to 4 spaces
2292
                x['string'] = x['name']
2293
2294
            result['toolbar'] = {
2295
                'print': resprint,
2296
                'action': resaction,
2297
                'relate': resrelate
2298
            }
2299
        return result
2300
957 by Olivier Laurent
pep8
2301
    _view_look_dom_arch = __view_look_dom_arch
922 by Christophe Simonis
convert tabs to 4 spaces
2302
2303
    def search_count(self, cr, user, args, context=None):
2304
        if not context:
2305
            context = {}
2306
        res = self.search(cr, user, args, context=context, count=True)
2307
        if isinstance(res, list):
2308
            return len(res)
2309
        return res
2310
2675 by Olivier Dony
[FIX] orm: fields_view_get selection values must properly heed ir.rules:
2311
    def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
2312
        """
2313
        Search for records based on a search domain.
2314
2315
        :param cr: database cursor
2316
        :param user: current user id
2317
        :param args: list of tuples specifying the search domain [('field_name', 'operator', value), ...]. Pass an empty list to match all records.
2318
        :param offset: optional number of results to skip in the returned values (default: 0)
2319
        :param limit: optional max number of records to return (default: **None**)
2320
        :param order: optional columns to sort by (default: self._order=id )
2321
        :param context: optional context arguments, like lang, time zone
2322
        :type context: dictionary
2323
        :param count: optional (default: **False**), if **True**, returns only the number of records matching the criteria, not their ids
2324
        :return: id or list of ids of records matching the criteria
2325
        :rtype: integer or list of integers
2326
        :raise AccessError: * if user tries to bypass access rules for read on the requested object.
2327
2328
        **Expressing a search domain (args)**
2329
2330
        Each tuple in the search domain needs to have 3 elements, in the form: **('field_name', 'operator', value)**, where:
2331
2332
            * **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.
2333
            * **operator** must be a string with a valid comparison operator from this list: ``=, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right``
2334
              The semantics of most of these operators are obvious.
2335
              The ``child_of`` operator will look for records who are children or grand-children of a given record,
2336
              according to the semantics of this model (i.e following the relationship field named by
2337
              ``self._parent_name``, by default ``parent_id``.
2338
            * **value** must be a valid value to compare with the values of **field_name**, depending on its type.
2339
2340
        Domain criteria can be combined using 3 logical operators than can be added between tuples:  '**&**' (logical AND, default), '**|**' (logical OR), '**!**' (logical NOT).
2341
        These are **prefix** operators and the arity of the '**&**' and '**|**' operator is 2, while the arity of the '**!**' is just 1.
2342
        Be very careful about this when you combine them the first time.
2343
2344
        Here is an example of searching for Partners named *ABC* from Belgium and Germany whose language is not english ::
2345
2346
            [('name','=','ABC'),'!',('language.code','=','en_US'),'|',('country_id.code','=','be'),('country_id.code','=','de'))
2347
2348
        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::
2349
2350
            (name is 'ABC' AND (language is NOT english) AND (country is Belgium OR Germany))
2351
2352
        """
2353
        return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
2354
922 by Christophe Simonis
convert tabs to 4 spaces
2355
    def name_get(self, cr, user, ids, context=None):
3353.1.42 by Olivier Dony
[IMP] orm.name*: better docstrings
2356
        """Returns the preferred display value (text representation) for the records with the
2357
           given ``ids``. By default this will be the value of the ``name`` column, unless
2358
           the model implements a custom behavior.
2359
           Can sometimes be seen as the inverse function of :meth:`~.name_search`, but it is not
2360
           guaranteed to be.
2361
2362
           :rtype: list(tuple)
2363
           :return: list of pairs ``(id,text_repr)`` for all records with the given ``ids``.
2364
        """
2676 by Olivier Dony
[IMP] orm: moved name_search + its private implementation + name_get from orm to orm_template
2365
        if not ids:
2366
            return []
2367
        if isinstance(ids, (int, long)):
2368
            ids = [ids]
4272.1.1 by Antony Lesuisse
[IMP] rec_name assertion and fallback
2369
4284.1.1 by Olivier Dony
[IMP] name_get: support more field types and improve previous name_get changes
2370
        if self._rec_name in self._all_columns:
2371
            rec_name_column = self._all_columns[self._rec_name].column
2372
            return [(r['id'], rec_name_column.as_display_name(cr, user, self, r[self._rec_name], context=context))
2373
                        for r in self.read(cr, user, ids, [self._rec_name],
2374
                                       load='_classic_write', context=context)]
2375
        return [(id, "%s,%s" % (self._name, id)) for id in ids]
922 by Christophe Simonis
convert tabs to 4 spaces
2376
1919.4.1 by Jay(Open ERP)
[IMP] Limit for name_search() improved to 100
2377
    def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
3353.1.42 by Olivier Dony
[IMP] orm.name*: better docstrings
2378
        """Search for records that have a display name matching the given ``name`` pattern if compared
2379
           with the given ``operator``, while also matching the optional search domain (``args``).
2380
           This is used for example to provide suggestions based on a partial value for a relational
2381
           field.
2382
           Sometimes be seen as the inverse function of :meth:`~.name_get`, but it is not
2383
           guaranteed to be.
2384
2385
           This method is equivalent to calling :meth:`~.search` with a search domain based on ``name``
2386
           and then :meth:`~.name_get` on the result of the search.
2387
2388
           :param list args: optional search domain (see :meth:`~.search` for syntax),
2389
                             specifying further restrictions
2390
           :param str operator: domain operator for matching the ``name`` pattern, such as ``'like'``
2391
                                or ``'='``.
2392
           :param int limit: optional max number of records to return
2393
           :rtype: list
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2394
           :return: list of pairs ``(id,text_repr)`` for all matching records.
2676 by Olivier Dony
[IMP] orm: moved name_search + its private implementation + name_get from orm to orm_template
2395
        """
2396
        return self._name_search(cr, user, name, args, operator, context, limit)
3454 by Nicolas Vanhoren
[MERGE] addition of name_create() ORM method for new m2o behavior in 6.1
2397
2398
    def name_create(self, cr, uid, name, context=None):
3353.1.42 by Olivier Dony
[IMP] orm.name*: better docstrings
2399
        """Creates a new record by calling :meth:`~.create` with only one
2400
           value provided: the name of the new record (``_rec_name`` field).
2401
           The new record will also be initialized with any default values applicable
2402
           to this model, or provided through the context. The usual behavior of
2403
           :meth:`~.create` applies.
2404
           Similarly, this method may raise an exception if the model has multiple
2405
           required fields and some do not have default values.
2406
2407
           :param name: name of the record to create
2408
2409
           :rtype: tuple
2410
           :return: the :meth:`~.name_get` pair value for the newly-created record.
3454 by Nicolas Vanhoren
[MERGE] addition of name_create() ORM method for new m2o behavior in 6.1
2411
        """
4682.2.12 by Xavier Morel
[REM] trailing semicolons
2412
        rec_id = self.create(cr, uid, {self._rec_name: name}, context)
3454 by Nicolas Vanhoren
[MERGE] addition of name_create() ORM method for new m2o behavior in 6.1
2413
        return self.name_get(cr, uid, [rec_id], context)[0]
2676 by Olivier Dony
[IMP] orm: moved name_search + its private implementation + name_get from orm to orm_template
2414
2415
    # private implementation of name_search, allows passing a dedicated user for the name_get part to
2416
    # solve some access rights issues
2417
    def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
2418
        if args is None:
2419
            args = []
2420
        if context is None:
2421
            context = {}
2422
        args = args[:]
3753 by Olivier Dony
[FIX] expression: proper handling of empty string in m2o domain criterions
2423
        # optimize out the default criterion of ``ilike ''`` that matches everything
2424
        if not (name == '' and operator == 'ilike'):
2676 by Olivier Dony
[IMP] orm: moved name_search + its private implementation + name_get from orm to orm_template
2425
            args += [(self._rec_name, operator, name)]
2426
        access_rights_uid = name_get_uid or user
2427
        ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
2428
        res = self.name_get(cr, access_rights_uid, ids, context)
2429
        return res
922 by Christophe Simonis
convert tabs to 4 spaces
2430
1314 by Stephane Wirtel
[FIX] Move the read_string and write_string methods from orm to orm_template
2431
    def read_string(self, cr, uid, id, langs, fields=None, context=None):
2432
        res = {}
2433
        res2 = {}
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
2434
        self.pool.get('ir.translation').check_access_rights(cr, uid, 'read')
1314 by Stephane Wirtel
[FIX] Move the read_string and write_string methods from orm to orm_template
2435
        if not fields:
2436
            fields = self._columns.keys() + self._inherit_fields.keys()
1884.1.4 by Harry (Open ERP)
[FIX] replaced <TAB> with four white space.
2437
        #FIXME: collect all calls to _get_source into one SQL call.
1314 by Stephane Wirtel
[FIX] Move the read_string and write_string methods from orm to orm_template
2438
        for lang in langs:
2439
            res[lang] = {'code': lang}
2440
            for f in fields:
2441
                if f in self._columns:
2442
                    res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
2443
                    if res_trans:
2444
                        res[lang][f] = res_trans
2445
                    else:
2446
                        res[lang][f] = self._columns[f].string
2447
        for table in self._inherits:
2448
            cols = intersect(self._inherit_fields.keys(), fields)
2449
            res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
2450
        for lang in res2:
2451
            if lang in res:
1559 by Stephane Wirtel
[FIX] Translation - read_string
2452
                res[lang]['code'] = lang
1314 by Stephane Wirtel
[FIX] Move the read_string and write_string methods from orm to orm_template
2453
            for f in res2[lang]:
2454
                res[lang][f] = res2[lang][f]
2455
        return res
2456
2457
    def write_string(self, cr, uid, id, langs, vals, context=None):
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
2458
        self.pool.get('ir.translation').check_access_rights(cr, uid, 'write')
1884.1.4 by Harry (Open ERP)
[FIX] replaced <TAB> with four white space.
2459
        #FIXME: try to only call the translation in one SQL
1314 by Stephane Wirtel
[FIX] Move the read_string and write_string methods from orm to orm_template
2460
        for lang in langs:
2461
            for field in vals:
2462
                if field in self._columns:
2238.1.20 by Anup(OpenERP)
[FIX] Export translations + Cache on translations + Resynchronization wizard Corrections
2463
                    src = self._columns[field].string
2464
                    self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field], src)
1314 by Stephane Wirtel
[FIX] Move the read_string and write_string methods from orm to orm_template
2465
        for table in self._inherits:
2466
            cols = intersect(self._inherit_fields.keys(), vals)
2467
            if cols:
2468
                self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2469
        return True
2470
2654 by Olivier Dony
[FIX,REF] orm: correct default values update during create() + refactor
2471
    def _add_missing_default_values(self, cr, uid, values, context=None):
2472
        missing_defaults = []
2473
        avoid_tables = [] # avoid overriding inherited values when parent is set
2474
        for tables, parent_field in self._inherits.items():
2475
            if parent_field in values:
2476
                avoid_tables.append(tables)
2477
        for field in self._columns.keys():
2478
            if not field in values:
2479
                missing_defaults.append(field)
2480
        for field in self._inherit_fields.keys():
2481
            if (field not in values) and (self._inherit_fields[field][0] not in avoid_tables):
2482
                missing_defaults.append(field)
2483
2484
        if len(missing_defaults):
2485
            # override defaults with the provided values, never allow the other way around
2486
            defaults = self.default_get(cr, uid, missing_defaults, context)
2487
            for dv in defaults:
3332 by Olivier Dony
[FIX] orm: incorrect braces when processing default values for o2m/m2m
2488
                if ((dv in self._columns and self._columns[dv]._type == 'many2many') \
2489
                     or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'many2many')) \
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
2490
                        and defaults[dv] and isinstance(defaults[dv][0], (int, long)):
2491
                    defaults[dv] = [(6, 0, defaults[dv])]
3332 by Olivier Dony
[FIX] orm: incorrect braces when processing default values for o2m/m2m
2492
                if (dv in self._columns and self._columns[dv]._type == 'one2many' \
2493
                    or (dv in self._inherit_fields and self._inherit_fields[dv][2]._type == 'one2many')) \
3310.7.1 by Jay Vora(OpenERP)
[FIX] orm: _add_missing_default_values: Allowing only valid x2m default values (Ref : Case 4364)
2494
                        and isinstance(defaults[dv], (list, tuple)) and defaults[dv] and isinstance(defaults[dv][0], dict):
2906.1.1 by Olivier Dony
[IMP] orm: _add_missing_default_values: handle o2m default values in the legacy form [{},{}] + inherited x2m defaults
2495
                    defaults[dv] = [(0, 0, x) for x in defaults[dv]]
2654 by Olivier Dony
[FIX,REF] orm: correct default values update during create() + refactor
2496
            defaults.update(values)
2497
            values = defaults
2498
        return values
2499
3443.1.16 by Vo Minh Thu
[IMP] tools.cache: added missing clean_caches_for_db replacement.
2500
    def clear_caches(self):
2501
        """ Clear the caches
2502
2503
        This clears the caches associated to methods decorated with
2504
        ``tools.ormcache`` or ``tools.ormcache_multi``.
2505
        """
2506
        try:
2507
            getattr(self, '_ormcache')
2508
            self._ormcache = {}
4644.1.1 by Antony Lesuisse
multiprocessing signaling manually backported from 6.1
2509
            self.pool._any_cache_cleared = True
3443.1.16 by Vo Minh Thu
[IMP] tools.cache: added missing clean_caches_for_db replacement.
2510
        except AttributeError:
2511
            pass
2512
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2513
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2514
    def _read_group_fill_results(self, cr, uid, domain, groupby, groupby_list, aggregated_fields,
2515
                                 read_group_result, read_group_order=None, context=None):
2516
        """Helper method for filling in empty groups for all possible values of
2517
           the field being grouped by"""
2518
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2519
        # self._group_by_full should map groupable fields to a method that returns
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2520
        # a list of all aggregated values that we want to display for this field,
2521
        # in the form of a m2o-like pair (key,label).
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2522
        # This is useful to implement kanban views for instance, where all columns
2523
        # should be displayed even if they don't contain any record.
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2524
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2525
        # Grab the list of all groups that should be displayed, including all present groups
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2526
        present_group_ids = [x[groupby][0] for x in read_group_result if x[groupby]]
4399 by Fabien Pinckaers
[IMP] folded columns in read_group
2527
        all_groups,folded = self._group_by_full[groupby](self, cr, uid, present_group_ids, domain,
3826 by Olivier Dony
[IMP] read_group: pass superuser uid to group_by_full methods to avoid access right issues
2528
                                                  read_group_order=read_group_order,
2529
                                                  access_rights_uid=openerp.SUPERUSER_ID,
2530
                                                  context=context)
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2531
3816 by Olivier Dony
[MERGE] orm.read_group: partial refactoring, by xmo
2532
        result_template = dict.fromkeys(aggregated_fields, False)
4263.3.25 by Xavier Morel
[FIX] weird dict.update calls
2533
        result_template[groupby + '_count'] = 0
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2534
        if groupby_list and len(groupby_list) > 1:
4263.3.25 by Xavier Morel
[FIX] weird dict.update calls
2535
            result_template['__context'] = {'group_by': groupby_list[1:]}
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2536
2537
        # Merge the left_side (current results as dicts) with the right_side (all
2538
        # possible values as m2o pairs). Both lists are supposed to be using the
2539
        # same ordering, and can be merged in one pass.
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2540
        result = []
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2541
        known_values = {}
2542
        def append_left(left_side):
2543
            grouped_value = left_side[groupby] and left_side[groupby][0]
2544
            if not grouped_value in known_values:
2545
                result.append(left_side)
2546
                known_values[grouped_value] = left_side
2547
            else:
2548
                count_attr = groupby + '_count'
2549
                known_values[grouped_value].update({count_attr: left_side[count_attr]})
2550
        def append_right(right_side):
2551
            grouped_value = right_side[0]
2552
            if not grouped_value in known_values:
2553
                line = dict(result_template)
4263.3.25 by Xavier Morel
[FIX] weird dict.update calls
2554
                line[groupby] = right_side
2555
                line['__domain'] = [(groupby,'=',grouped_value)] + domain
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2556
                result.append(line)
2557
                known_values[grouped_value] = line
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2558
        while read_group_result or all_groups:
2559
            left_side = read_group_result[0] if read_group_result else None
2560
            right_side = all_groups[0] if all_groups else None
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2561
            assert left_side is None or left_side[groupby] is False \
2562
                 or isinstance(left_side[groupby], (tuple,list)), \
2563
                'M2O-like pair expected, got %r' % left_side[groupby]
2564
            assert right_side is None or isinstance(right_side, (tuple,list)), \
2565
                'M2O-like pair expected, got %r' % right_side
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2566
            if left_side is None:
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2567
                append_right(all_groups.pop(0))
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2568
            elif right_side is None:
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2569
                append_left(read_group_result.pop(0))
2570
            elif left_side[groupby] == right_side:
2571
                append_left(read_group_result.pop(0))
2572
                all_groups.pop(0) # discard right_side
2573
            elif not left_side[groupby] or not left_side[groupby][0]:
2574
                # left side == "Undefined" entry, not present on right_side
2575
                append_left(read_group_result.pop(0))
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2576
            else:
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2577
                append_right(all_groups.pop(0))
4400 by Fabien Pinckaers
fix
2578
4399 by Fabien Pinckaers
[IMP] folded columns in read_group
2579
        if folded:
2580
            for r in result:
4400 by Fabien Pinckaers
fix
2581
                r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
3814 by Olivier Dony
[FIX] orm.read_group: fix issues in read_group_full and rewrite/cleanup
2582
        return result
2583
3029.1.2 by Thibault Francois
[FIX] orm : webclient pass context insted order by, arrange order of args
2584
    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2585
        """
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
2586
        Get the list of records in list view grouped by the given ``groupby`` fields
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2587
2588
        :param cr: database cursor
2589
        :param uid: current user id
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
2590
        :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
3404 by Xavier Morel
[IMP] fix wording of read_group documentation
2591
        :param list fields: list of fields present in the list view specified on the object
2592
        :param list groupby: fields by which the records will be grouped
2593
        :param int offset: optional number of records to skip
2594
        :param int limit: optional max number of records to return
2595
        :param dict context: context arguments, like lang, time zone
3508 by Xavier Morel
[FIX] name of ordering param in read_group docstring: it's orderby not order
2596
        :param list orderby: optional ``order by`` specification, for
2597
                             overriding the natural sort ordering of the
2598
                             groups, see also :py:meth:`~osv.osv.osv.search`
2599
                             (supported only for many2one fields currently)
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
2600
        :return: list of dictionaries(one dictionary for each record) containing:
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2601
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
2602
                    * the values of fields grouped by the fields in ``groupby`` argument
2603
                    * __domain: list of tuples specifying the search criteria
2604
                    * __context: dictionary with argument like ``groupby``
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2605
        :rtype: [{'field_name_1': value, ...]
2606
        :raise AccessError: * if user has no read rights on the requested object
2607
                            * if user tries to bypass access rules for read on the requested object
2608
2609
        """
1981 by HDA(OpenERP)
[IMP] group by functionality
2610
        context = context or {}
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
2611
        self.check_access_rights(cr, uid, 'read')
1981 by HDA(OpenERP)
[IMP] group by functionality
2612
        if not fields:
1986 by Fabien Pinckaers
[IMP] read_group
2613
            fields = self._columns.keys()
2089.1.9 by Fabien Pinckaers
replaced tab by white spaces
2614
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
2615
        query = self._where_calc(cr, uid, domain, context=context)
2616
        self._apply_ir_rules(cr, uid, query, 'read', context=context)
2089.1.9 by Fabien Pinckaers
replaced tab by white spaces
2617
2089.1.2 by Olivier Dony
[FIX] attempt at supporting inherited fields (through _inherits) as group_by in read_group()
2618
        # Take care of adding join(s) if groupby is an '_inherits'ed field
2188 by Fabien Pinckaers
[IMP] group_read
2619
        groupby_list = groupby
3269 by Olivier Dony
[FIX] orm.read_group: prepend fields with table names to avoid collisions in SQL queries
2620
        qualified_groupby_field = groupby
2188 by Fabien Pinckaers
[IMP] group_read
2621
        if groupby:
2728.1.13 by Olivier Dony
[IMP] orm.read_group: implement simple ordering of groups
2622
            if isinstance(groupby, list):
2188 by Fabien Pinckaers
[IMP] group_read
2623
                groupby = groupby[0]
3269 by Olivier Dony
[FIX] orm.read_group: prepend fields with table names to avoid collisions in SQL queries
2624
            qualified_groupby_field = self._inherits_join_calc(groupby, query)
2089.1.9 by Fabien Pinckaers
replaced tab by white spaces
2625
3122 by Olivier Dony
[IMP] orm.read_group: raise if called with group_by being a non-database-stored field
2626
        if groupby:
2627
            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?)"
2628
            groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
2629
            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"
2725 by Olivier Dony
[IMP] orm.read_group: remove sql injection vector by forcing group_by to be valid field + assert grouped field is in the view
2630
3466.1.9 by Vo Minh Thu
[REF] orm: added TODOs.
2631
        # TODO it seems fields_get can be replaced by _all_columns (no need for translation)
1986 by Fabien Pinckaers
[IMP] read_group
2632
        fget = self.fields_get(cr, uid, fields)
2188 by Fabien Pinckaers
[IMP] group_read
2633
        flist = ''
3003.1.1 by RGA(OpenERP)
[IMP] group by header should display how many children it has
2634
        group_count = group_by = groupby
2188 by Fabien Pinckaers
[IMP] group_read
2635
        if groupby:
2728.1.13 by Olivier Dony
[IMP] orm.read_group: implement simple ordering of groups
2636
            if fget.get(groupby):
3900 by Olivier Dony
[FIX] read_group: NULL->False for grouped boolean fields
2637
                groupby_type = fget[groupby]['type']
2638
                if groupby_type in ('date', 'datetime'):
2639
                    qualified_groupby_field = "to_char(%s,'yyyy-mm')" % qualified_groupby_field
2640
                    flist = "%s as %s " % (qualified_groupby_field, groupby)
2641
                elif groupby_type == 'boolean':
2642
                    qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
2643
                    flist = "%s as %s " % (qualified_groupby_field, groupby)
2725 by Olivier Dony
[IMP] orm.read_group: remove sql injection vector by forcing group_by to be valid field + assert grouped field is in the view
2644
                else:
3269 by Olivier Dony
[FIX] orm.read_group: prepend fields with table names to avoid collisions in SQL queries
2645
                    flist = qualified_groupby_field
2188 by Fabien Pinckaers
[IMP] group_read
2646
            else:
2743 by nch at tinyerp
[FIX]:default sorting in group by
2647
                # Don't allow arbitrary values, as this would be a SQL injection vector!
2725 by Olivier Dony
[IMP] orm.read_group: remove sql injection vector by forcing group_by to be valid field + assert grouped field is in the view
2648
                raise except_orm(_('Invalid group_by'),
2649
                                 _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
2188 by Fabien Pinckaers
[IMP] group_read
2650
3806.1.1 by Xavier Morel
[IMP] selection of fields to aggregate in read_group
2651
        aggregated_fields = [
2652
            f for f in fields
2653
            if f not in ('id', 'sequence')
2654
            if fget[f]['type'] in ('integer', 'float')
3816 by Olivier Dony
[MERGE] orm.read_group: partial refactoring, by xmo
2655
            if (f in self._columns and getattr(self._columns[f], '_classic_write'))]
3806.1.1 by Xavier Morel
[IMP] selection of fields to aggregate in read_group
2656
        for f in aggregated_fields:
2657
            group_operator = fget[f].get('group_operator', 'sum')
2658
            if flist:
2659
                flist += ', '
2660
            qualified_field = '"%s"."%s"' % (self._table, f)
2661
            flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
2089.1.9 by Fabien Pinckaers
replaced tab by white spaces
2662
3269 by Olivier Dony
[FIX] orm.read_group: prepend fields with table names to avoid collisions in SQL queries
2663
        gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
2664
2665
        from_clause, where_clause, where_clause_params = query.get_sql()
2666
        where_clause = where_clause and ' WHERE ' + where_clause
2667
        limit_str = limit and ' limit %d' % limit or ''
2668
        offset_str = offset and ' offset %d' % offset or ''
3003.1.1 by RGA(OpenERP)
[IMP] group by header should display how many children it has
2669
        if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
2670
            group_count = '_'
3134 by Olivier Dony
[FIX] read_group: better support for empty 'fields' or 'groupby' parameters
2671
        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)
1986 by Fabien Pinckaers
[IMP] read_group
2672
        alldata = {}
1869.1.50 by nch at tinyerp
[FIX]:date display format
2673
        groupby = group_by
1986 by Fabien Pinckaers
[IMP] read_group
2674
        for r in cr.dictfetchall():
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2675
            for fld, val in r.items():
4682.2.2 by Xavier Morel
[IMP] compare to None by identity
2676
                if val is None: r[fld] = False
1986 by Fabien Pinckaers
[IMP] read_group
2677
            alldata[r['id']] = r
2678
            del r['id']
3070 by Olivier Dony
[FIX] orm.read_group: support ordering by any field (not just m2o ones)
2679
3821 by Olivier Dony
[FIX] orm.read_group: fix merge logic for read_group_full:
2680
        order = orderby or groupby
2681
        data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=order, context=context)
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
2682
4066.1.143 by Olivier Dony
[FIX] read_group: empty `groupby` case broken by last read_group patch
2683
        # the IDs of records that have groupby field value = False or '' should be included too
2684
        data_ids += set(alldata.keys()).difference(data_ids)
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
2685
2686
        if groupby:
4066.1.143 by Olivier Dony
[FIX] read_group: empty `groupby` case broken by last read_group patch
2687
            data = self.read(cr, uid, data_ids, [groupby], context=context)
2688
            # restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
2689
            data_dict = dict((d['id'], d[groupby] ) for d in data)
2690
            result = [{'id': i, groupby: data_dict[i]} for i in data_ids]
2691
        else:
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
2692
            result = [{'id': i} for i in data_ids]
3070 by Olivier Dony
[FIX] orm.read_group: support ordering by any field (not just m2o ones)
2693
4066.1.135 by Olivier Dony
[FIX] read_group: remove prohibitive n^2 operations, could freeze the server on large group_by results
2694
        for d in result:
2188 by Fabien Pinckaers
[IMP] group_read
2695
            if groupby:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2696
                d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
2697
                if not isinstance(groupby_list, (str, unicode)):
2205 by nch at tinyerp
[IMP]:group_by to support no_leaf in multi level group by
2698
                    if groupby or not context.get('group_by_no_leaf', False):
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2699
                        d['__context'] = {'group_by': groupby_list[1:]}
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
2700
            if groupby and groupby in fget:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2701
                if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
2702
                    dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2703
                    days = calendar.monthrange(dt.year, dt.month)[1]
1869.1.50 by nch at tinyerp
[FIX]:date display format
2704
3790.1.1 by Xavier Morel
[IMP] Use babel's locale-aware date formatting when formatting dates in read_group titles
2705
                    date_value = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d')
2706
                    d[groupby] = babel.dates.format_date(
2707
                        date_value, format='MMMM yyyy', locale=context.get('lang', 'en_US'))
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2708
                    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),\
2709
                                     (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
2188 by Fabien Pinckaers
[IMP] group_read
2710
                del alldata[d['id']][groupby]
1986 by Fabien Pinckaers
[IMP] read_group
2711
            d.update(alldata[d['id']])
2712
            del d['id']
3791 by Fabien Pinckaers
[IMP] _group_by_full implementation
2713
3817 by Olivier Dony
[FIX] orm.read_group: check for empty groupby list
2714
        if groupby and groupby in self._group_by_full:
4066.1.135 by Olivier Dony
[FIX] read_group: remove prohibitive n^2 operations, could freeze the server on large group_by results
2715
            result = self._read_group_fill_results(cr, uid, domain, groupby, groupby_list,
2716
                                                   aggregated_fields, result, read_group_order=order,
2717
                                                   context=context)
3816 by Olivier Dony
[MERGE] orm.read_group: partial refactoring, by xmo
2718
4066.1.135 by Olivier Dony
[FIX] read_group: remove prohibitive n^2 operations, could freeze the server on large group_by results
2719
        return result
1981 by HDA(OpenERP)
[IMP] group by functionality
2720
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
2721
    def _inherits_join_add(self, current_model, parent_model_name, query):
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2722
        """
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
2723
        Add missing table SELECT and JOIN clause to ``query`` for reaching the parent table (no duplicates)
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
2724
        :param current_model: current model object
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2725
        :param parent_model_name: name of the parent model for which the clauses should be added
2793.1.3 by Olivier Dony
[IMP] osv.query,orm: removed trailing whitespace introduced by previous commits
2726
        :param query: query object on which the JOIN should be added
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2727
        """
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
2728
        inherits_field = current_model._inherits[parent_model_name]
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2729
        parent_model = self.pool.get(parent_model_name)
4601.2.44 by Thibault Delavallée
[CLEAN] Query: cleaned a bit the code. All joins now goes through the same method, either implicit or explicit. Will have to be upgraded in future versions, but at least this is a bit centralized. Updated ORM accordingly. Updated tests. Added a get_alias_from_query method in expression that find the table and the alias from a 'full alias' statement.
2730
        parent_alias, parent_alias_statement = query.add_join((current_model._table, parent_model._table, inherits_field, 'id', inherits_field), implicit=True)
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
2731
        return parent_alias
3549.4.1 by Naresh(OpenERP)
[FIX]:_inherits for multilevel inheritence
2732
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
2733
    def _inherits_join_calc(self, field, query):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
2734
        """
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
2735
        Adds missing table select and join clause(s) to ``query`` for reaching
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2736
        the field coming from an '_inherits' parent table (no duplicates).
2737
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
2738
        :param field: name of inherited field to reach
2739
        :param query: query object on which the JOIN should be added
2740
        :return: qualified name of field, to be used in SELECT clause
2089.1.2 by Olivier Dony
[FIX] attempt at supporting inherited fields (through _inherits) as group_by in read_group()
2741
        """
2742
        current_table = self
4601.2.48 by Thibault Delavallée
[CLEAN] orm: added quote around a forgottent table name; cleaned a bit some code and added comments, removed dead code.
2743
        parent_alias = '"%s"' % current_table._table
2089.1.2 by Olivier Dony
[FIX] attempt at supporting inherited fields (through _inherits) as group_by in read_group()
2744
        while field in current_table._inherit_fields and not field in current_table._columns:
2626 by Olivier Dony
[IMP/REF] orm: added support of ir.rules coming from _inherits parents
2745
            parent_model_name = current_table._inherit_fields[field][0]
2746
            parent_table = self.pool.get(parent_model_name)
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
2747
            parent_alias = self._inherits_join_add(current_table, parent_model_name, query)
2089.1.2 by Olivier Dony
[FIX] attempt at supporting inherited fields (through _inherits) as group_by in read_group()
2748
            current_table = parent_table
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
2749
        return '%s."%s"' % (parent_alias, field)
2089.1.2 by Olivier Dony
[FIX] attempt at supporting inherited fields (through _inherits) as group_by in read_group()
2750
974 by Fabien Pinckaers
Speed Improvement:
2751
    def _parent_store_compute(self, cr):
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
2752
        if not self._parent_store:
2753
            return
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
2754
        _logger.info('Computing parent left and right for table %s...', self._table)
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2755
        def browse_rec(root, pos=0):
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
2756
            # TODO: set order
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2757
            where = self._parent_name+'='+str(root)
2758
            if not root:
2759
                where = self._parent_name+' IS NULL'
1595.1.7 by Fabien Pinckaers
bugfixes
2760
            if self._parent_order:
2761
                where += ' order by '+self._parent_order
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2762
            cr.execute('SELECT id FROM '+self._table+' WHERE '+where)
2763
            pos2 = pos + 1
3006 by Numerigraphe - Lionel Sausin
[FIX] incorrect spellings (childs -> children)
2764
            for id in cr.fetchall():
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2765
                pos2 = browse_rec(id[0], pos2)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2766
            cr.execute('update '+self._table+' set parent_left=%s, parent_right=%s where id=%s', (pos, pos2, root))
2767
            return pos2 + 1
1640.2.6 by Fabien Pinckaers
bugfix_ptt
2768
        query = 'SELECT id FROM '+self._table+' WHERE '+self._parent_name+' IS NULL'
2769
        if self._parent_order:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2770
            query += ' order by ' + self._parent_order
1640.2.6 by Fabien Pinckaers
bugfix_ptt
2771
        pos = 0
2772
        cr.execute(query)
2773
        for (root,) in cr.fetchall():
2774
            pos = browse_rec(root, pos)
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2775
        return True
2776
1398 by Fabien Pinckaers
improvements_bugfixes
2777
    def _update_store(self, cr, f, k):
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
2778
        _logger.info("storing computed values of fields.function '%s'", k)
1398 by Fabien Pinckaers
improvements_bugfixes
2779
        ss = self._columns[k]._symbol_set
2780
        update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2781
        cr.execute('select id from '+self._table)
2782
        ids_lst = map(lambda x: x[0], cr.fetchall())
2783
        while ids_lst:
2784
            iids = ids_lst[:40]
2785
            ids_lst = ids_lst[40:]
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
2786
            res = f.get(cr, self, iids, k, SUPERUSER_ID, {})
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2787
            for key, val in res.items():
1398 by Fabien Pinckaers
improvements_bugfixes
2788
                if f._multi:
2789
                    val = val[k]
2790
                # if val is a many2one, just write the ID
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2791
                if type(val) == tuple:
1398 by Fabien Pinckaers
improvements_bugfixes
2792
                    val = val[0]
3974.2.3 by Florent Xicluna
[REF] replace deprecated <> symbol.
2793
                if val is not False:
1398 by Fabien Pinckaers
improvements_bugfixes
2794
                    cr.execute(update_query, (ss[1](val), key))
2795
3129.1.2 by Olivier Dony
[FIX] fields.reference: don't accept half-defined references, and avoid crashing on previous bad ones
2796
    def _check_selection_field_value(self, cr, uid, field, value, context=None):
2797
        """Raise except_orm if value is not among the valid values for the selection field"""
2798
        if self._columns[field]._type == 'reference':
2799
            val_model, val_id_str = value.split(',', 1)
2800
            val_id = False
2801
            try:
2802
                val_id = long(val_id_str)
2803
            except ValueError:
2804
                pass
2805
            if not val_id:
2806
                raise except_orm(_('ValidateError'),
3630 by Vo Minh Thu
[MERGE] orm: better exception messsages.
2807
                                 _('Invalid value for reference field "%s.%s" (last part must be a non-zero integer): "%s"') % (self._table, field, value))
3129.1.2 by Olivier Dony
[FIX] fields.reference: don't accept half-defined references, and avoid crashing on previous bad ones
2808
            val = val_model
2809
        else:
2810
            val = value
2811
        if isinstance(self._columns[field].selection, (tuple, list)):
2812
            if val in dict(self._columns[field].selection):
2813
                return
2814
        elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
2815
            return
2816
        raise except_orm(_('ValidateError'),
3974.2.2 by Florent Xicluna
[REF] fix weird indentation, not multiple of four.
2817
                         _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._table, field))
3129.1.2 by Olivier Dony
[FIX] fields.reference: don't accept half-defined references, and avoid crashing on previous bad ones
2818
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2819
    def _check_removed_columns(self, cr, log=False):
2820
        # iterate on the database columns to drop the NOT NULL constraints
2821
        # of fields which were required but have been removed (or will be added by another module)
2822
        columns = [c for c in self._columns if not (isinstance(self._columns[c], fields.function) and not self._columns[c].store)]
3647 by Olivier Dony
[FIX] orm,expression: sanity checks for domain terms, cleanup, tests
2823
        columns += MAGIC_COLUMNS
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2824
        cr.execute("SELECT a.attname, a.attnotnull"
2825
                   "  FROM pg_class c, pg_attribute a"
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
2826
                   " WHERE c.relname=%s"
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2827
                   "   AND c.oid=a.attrelid"
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
2828
                   "   AND a.attisdropped=%s"
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2829
                   "   AND pg_catalog.format_type(a.atttypid, a.atttypmod) NOT IN ('cid', 'tid', 'oid', 'xid')"
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2830
                   "   AND a.attname NOT IN %s", (self._table, False, tuple(columns))),
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
2831
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2832
        for column in cr.dictfetchall():
2833
            if log:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
2834
                _logger.debug("column %s is in the table %s but not in the corresponding object %s",
2835
                              column['attname'], self._table, self._name)
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2836
            if column['attnotnull']:
2837
                cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, column['attname']))
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
2838
                _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2839
                              self._table, column['attname'])
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2840
4178.1.1 by Vo Minh Thu
[IMP] uninstall: started test module, added new ir_model_constraint table.
2841
    def _save_constraint(self, cr, constraint_name, type):
4178.1.4 by Vo Minh Thu
[IMP] uninstall: added some docstrings.
2842
        """
2843
        Record the creation of a constraint for this model, to make it possible
2844
        to delete it later when the module is uninstalled. Type can be either
2845
        'f' or 'u' depending on the constraing being a foreign key or not.
2846
        """
4178.1.1 by Vo Minh Thu
[IMP] uninstall: started test module, added new ir_model_constraint table.
2847
        assert type in ('f', 'u')
2848
        cr.execute("""
2849
            SELECT 1 FROM ir_model_constraint, ir_module_module
2850
            WHERE ir_model_constraint.module=ir_module_module.id
2851
                AND ir_model_constraint.name=%s
2852
                AND ir_module_module.name=%s
2853
            """, (constraint_name, self._module))
2854
        if not cr.rowcount:
2855
            cr.execute("""
2856
                INSERT INTO ir_model_constraint
2857
                    (name, date_init, date_update, module, model, type)
2858
                VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
2859
                    (SELECT id FROM ir_module_module WHERE name=%s),
2860
                    (SELECT id FROM ir_model WHERE model=%s), %s)""",
2861
                    (constraint_name, self._module, self._name, type))
2862
4178.1.3 by Vo Minh Thu
[IMP] uninstall: use a dedicated table for many2many relationships instead of overlaoding ir.model.data.
2863
    def _save_relation_table(self, cr, relation_table):
4178.1.4 by Vo Minh Thu
[IMP] uninstall: added some docstrings.
2864
        """
2865
        Record the creation of a many2many for this model, to make it possible
2866
        to delete it later when the module is uninstalled.
2867
        """
4178.1.3 by Vo Minh Thu
[IMP] uninstall: use a dedicated table for many2many relationships instead of overlaoding ir.model.data.
2868
        cr.execute("""
2869
            SELECT 1 FROM ir_model_relation, ir_module_module
2870
            WHERE ir_model_relation.module=ir_module_module.id
2871
                AND ir_model_relation.name=%s
2872
                AND ir_module_module.name=%s
2873
            """, (relation_table, self._module))
4074.1.27 by Olivier Dony
[IMP] orm,ir.model: improve registration and deletion of schema ext_ids
2874
        if not cr.rowcount:
4178.1.3 by Vo Minh Thu
[IMP] uninstall: use a dedicated table for many2many relationships instead of overlaoding ir.model.data.
2875
            cr.execute("""INSERT INTO ir_model_relation (name, date_init, date_update, module, model)
2876
                                 VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
2877
                    (SELECT id FROM ir_module_module WHERE name=%s),
2878
                    (SELECT id FROM ir_model WHERE model=%s))""",
2879
                       (relation_table, self._module, self._name))
4074.1.27 by Olivier Dony
[IMP] orm,ir.model: improve registration and deletion of schema ext_ids
2880
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
2881
    # checked version: for direct m2o starting from `self`
2882
    def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete):
2883
        assert self.is_transient() or not dest_model.is_transient(), \
2884
            'Many2One relationships from non-transient Model to TransientModel are forbidden'
2885
        if self.is_transient() and not dest_model.is_transient():
2886
            # TransientModel relationships to regular Models are annoying
2887
            # usually because they could block deletion due to the FKs.
2888
            # So unless stated otherwise we default them to ondelete=cascade.
2889
            ondelete = ondelete or 'cascade'
4701 by Olivier Dony
[FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times
2890
        fk_def = (self._table, source_field, dest_model._table, ondelete or 'set null')
2891
        self._foreign_keys.add(fk_def)
2892
        _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", *fk_def)
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
2893
2894
    # unchecked version: for custom cases, such as m2m relationships
2895
    def _m2o_add_foreign_key_unchecked(self, source_table, source_field, dest_model, ondelete):
4701 by Olivier Dony
[FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times
2896
        fk_def = (source_table, source_field, dest_model._table, ondelete or 'set null')
2897
        self._foreign_keys.add(fk_def)
2898
        _schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s", *fk_def)
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
2899
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2900
    def _drop_constraint(self, cr, source_table, constraint_name):
2901
        cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (source_table,constraint_name))
2902
2903
    def _m2o_fix_foreign_key(self, cr, source_table, source_field, dest_model, ondelete):
2904
        # Find FK constraint(s) currently established for the m2o field,
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2905
        # and see whether they are stale or not
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2906
        cr.execute("""SELECT confdeltype as ondelete_rule, conname as constraint_name,
2907
                             cl2.relname as foreign_table
2908
                      FROM pg_constraint as con, pg_class as cl1, pg_class as cl2,
2909
                           pg_attribute as att1, pg_attribute as att2
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2910
                      WHERE con.conrelid = cl1.oid
2911
                        AND cl1.relname = %s
2912
                        AND con.confrelid = cl2.oid
2913
                        AND array_lower(con.conkey, 1) = 1
2914
                        AND con.conkey[1] = att1.attnum
2915
                        AND att1.attrelid = cl1.oid
2916
                        AND att1.attname = %s
2917
                        AND array_lower(con.confkey, 1) = 1
2918
                        AND con.confkey[1] = att2.attnum
2919
                        AND att2.attrelid = cl2.oid
2920
                        AND att2.attname = %s
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2921
                        AND con.contype = 'f'""", (source_table, source_field, 'id'))
2922
        constraints = cr.dictfetchall()
2923
        if constraints:
2924
            if len(constraints) == 1:
2925
                # Is it the right constraint?
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
2926
                cons, = constraints
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2927
                if cons['ondelete_rule'] != POSTGRES_CONFDELTYPES.get((ondelete or 'set null').upper(), 'a')\
2928
                    or cons['foreign_table'] != dest_model._table:
4701 by Olivier Dony
[FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times
2929
                    # Wrong FK: drop it and recreate
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2930
                    _schema.debug("Table '%s': dropping obsolete FK constraint: '%s'",
2931
                                  source_table, cons['constraint_name'])
2932
                    self._drop_constraint(cr, source_table, cons['constraint_name'])
4701 by Olivier Dony
[FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times
2933
                else:
2934
                    # it's all good, nothing to do!
2935
                    return
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2936
            else:
2937
                # Multiple FKs found for the same field, drop them all, and re-create
2938
                for cons in constraints:
2939
                    _schema.debug("Table '%s': dropping duplicate FK constraints: '%s'",
2940
                                  source_table, cons['constraint_name'])
2941
                    self._drop_constraint(cr, source_table, cons['constraint_name'])
4701 by Olivier Dony
[FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times
2942
2943
        # (re-)create the FK
2944
        self._m2o_add_foreign_key_checked(source_field, dest_model, ondelete)
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
2945
2946
2947
2537.1.2 by Numerigraphe - Lionel Sausin
[FIX] mutable default in osv
2948
    def _auto_init(self, cr, context=None):
3432.1.4 by Vo Minh Thu
[IMP] osv: removed unnecessary module arg, added comments.
2949
        """
2950
2951
        Call _field_create and, unless _auto is False:
2952
2953
        - create the corresponding table in database for the model,
2954
        - possibly add the parent columns in database,
2955
        - possibly add the columns 'create_uid', 'create_date', 'write_uid',
2956
          'write_date' in database if _log_access is True (the default),
2957
        - report on database columns no more existing in _columns,
2958
        - remove no more existing not null constraints,
2959
        - alter existing database columns to match _columns,
2960
        - create database tables to match _columns,
2961
        - add database indices to match _columns,
3453.2.2 by Vo Minh Thu
[IMP] orm: added comment, and delete an attribute when it is no more needed.
2962
        - save in self._foreign_keys a list a foreign keys to create (see
2963
          _auto_end).
3432.1.4 by Vo Minh Thu
[IMP] osv: removed unnecessary module arg, added comments.
2964
2965
        """
4701 by Olivier Dony
[FIX] orm,registry: properly check m2o FKs during model update + fix some models `auto_init`ed multiple times
2966
        self._foreign_keys = set()
3417.6.13 by Vo Minh Thu
[IMP] orm: check for object _name validity.
2967
        raise_on_invalid_object_name(self._name)
2537.1.2 by Numerigraphe - Lionel Sausin
[FIX] mutable default in osv
2968
        if context is None:
2969
            context = {}
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2970
        store_compute = False
1398 by Fabien Pinckaers
improvements_bugfixes
2971
        todo_end = []
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2972
        update_custom_fields = context.get('update_custom_fields', False)
922 by Christophe Simonis
convert tabs to 4 spaces
2973
        self._field_create(cr, context=context)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2974
        create = not self._table_exist(cr)
1958.2.5 by Xavier Morel
[imp] replace hasattr-based attribute selection by getattr
2975
        if getattr(self, '_auto', True):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2976
2977
            if create:
2978
                self._create_table(cr)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
2979
922 by Christophe Simonis
convert tabs to 4 spaces
2980
            cr.commit()
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2981
            if self._parent_store:
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2982
                if not self._parent_columns_exist(cr):
2983
                    self._create_parent_columns(cr)
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
2984
                    store_compute = True
2985
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2986
            # Create the create_uid, create_date, write_uid, write_date, columns if desired.
922 by Christophe Simonis
convert tabs to 4 spaces
2987
            if self._log_access:
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2988
                self._add_log_columns(cr)
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
2989
1673 by Christophe Simonis
[IMP] the module graph is updated and loaded until there are modules to load.
2990
            self._check_removed_columns(cr, log=False)
922 by Christophe Simonis
convert tabs to 4 spaces
2991
2992
            # iterate on the "object columns"
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2993
            column_data = self._select_column_data(cr)
2994
2995
            for k, f in self._columns.iteritems():
3647 by Olivier Dony
[FIX] orm,expression: sanity checks for domain terms, cleanup, tests
2996
                if k in MAGIC_COLUMNS:
922 by Christophe Simonis
convert tabs to 4 spaces
2997
                    continue
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
2998
                # Don't update custom (also called manual) fields
2999
                if f.manual and not update_custom_fields:
1844.4.103 by Jay(Open ERP)
[FIX] Custom fields won't disturb anything while updating server
3000
                    continue
2967 by Fabien Pinckaers
[IMP] Speed improvement when loading modules
3001
922 by Christophe Simonis
convert tabs to 4 spaces
3002
                if isinstance(f, fields.one2many):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3003
                    self._o2m_raise_on_missing_reference(cr, f)
3004
922 by Christophe Simonis
convert tabs to 4 spaces
3005
                elif isinstance(f, fields.many2many):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3006
                    self._m2m_raise_or_create_relation(cr, f)
3007
922 by Christophe Simonis
convert tabs to 4 spaces
3008
                else:
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3009
                    res = column_data.get(k)
3010
3011
                    # The field is not found as-is in database, try if it
3012
                    # exists with an old name.
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3013
                    if not res and hasattr(f, 'oldname'):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3014
                        res = column_data.get(f.oldname)
3015
                        if res:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3016
                            cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3017
                            res['attname'] = k
3018
                            column_data[k] = res
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3019
                            _schema.debug("Table '%s': renamed column '%s' to '%s'",
3020
                                self._table, f.oldname, k)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3021
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3022
                    # The field already exists in database. Possibly
3023
                    # change its type, rename it, drop it or change its
3024
                    # constraints.
3025
                    if res:
3026
                        f_pg_type = res['typname']
3027
                        f_pg_size = res['size']
3028
                        f_pg_notnull = res['attnotnull']
1958.2.5 by Xavier Morel
[imp] replace hasattr-based attribute selection by getattr
3029
                        if isinstance(f, fields.function) and not f.store and\
3030
                                not getattr(f, 'nodrop', False):
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3031
                            _logger.info('column %s (%s) in table %s removed: converted to a function !\n',
3032
                                         k, f.string, self._table)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3033
                            cr.execute('ALTER TABLE "%s" DROP COLUMN "%s" CASCADE' % (self._table, k))
1400 by Fabien Pinckaers
improvement
3034
                            cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3035
                            _schema.debug("Table '%s': dropped column '%s' with cascade",
3036
                                self._table, k)
922 by Christophe Simonis
convert tabs to 4 spaces
3037
                            f_obj_type = None
3038
                        else:
3039
                            f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
3040
3041
                        if f_obj_type:
1398 by Fabien Pinckaers
improvements_bugfixes
3042
                            ok = False
3043
                            casts = [
3579.1.8 by Xavier Morel
[FIX] typo, might want to launch the software from time to time while changing stuff
3044
                                ('text', 'char', pg_varchar(f.size), '::%s' % pg_varchar(f.size)),
1398 by Fabien Pinckaers
improvements_bugfixes
3045
                                ('varchar', 'text', 'TEXT', ''),
3046
                                ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
3047
                                ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
2445 by Fabien Pinckaers
[IMP] automated migration
3048
                                ('timestamp', 'date', 'date', '::date'),
1844.1.18 by uco(OpenERP)
[FIX] Update Module : Float8 to float and numeric to float casting made possible
3049
                                ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
3050
                                ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
1398 by Fabien Pinckaers
improvements_bugfixes
3051
                            ]
4466.1.3 by Christophe Simonis
[FIX] orm: handle fields.char() with not size correctly
3052
                            if f_pg_type == 'varchar' and f._type == 'char' and ((f.size is None and f_pg_size) or f_pg_size < f.size):
1398 by Fabien Pinckaers
improvements_bugfixes
3053
                                cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
3579.1.2 by Xavier Morel
[IMP] extract VARCHAR typing, if no size is provided (or the size is 0) don't put a limit on the varchar
3054
                                cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, pg_varchar(f.size)))
3055
                                cr.execute('UPDATE "%s" SET "%s"=temp_change_size::%s' % (self._table, k, pg_varchar(f.size)))
1844.4.20 by Jay(Open ERP)
[FIX] Upgrade could have failed when its a change in name field,added cascade
3056
                                cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
1398 by Fabien Pinckaers
improvements_bugfixes
3057
                                cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3058
                                _schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
4466.1.3 by Christophe Simonis
[FIX] orm: handle fields.char() with not size correctly
3059
                                    self._table, k, f_pg_size or 'unlimited', f.size or 'unlimited')
1398 by Fabien Pinckaers
improvements_bugfixes
3060
                            for c in casts:
3061
                                if (f_pg_type==c[0]) and (f._type==c[1]):
2106 by Fabien Pinckaers
[IMP] digits_compute to change size of float fields on the fly
3062
                                    if f_pg_type != f_obj_type:
1844.1.18 by uco(OpenERP)
[FIX] Update Module : Float8 to float and numeric to float casting made possible
3063
                                        ok = True
3064
                                        cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
3065
                                        cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2]))
3066
                                        cr.execute(('UPDATE "%s" SET "%s"=temp_change_size'+c[3]) % (self._table, k))
3067
                                        cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
3068
                                        cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3069
                                        _schema.debug("Table '%s': column '%s' changed type from %s to %s",
2707 by Xavier Morel
[IMP] logging in orm
3070
                                            self._table, k, c[0], c[1])
1844.1.18 by uco(OpenERP)
[FIX] Update Module : Float8 to float and numeric to float casting made possible
3071
                                    break
1398 by Fabien Pinckaers
improvements_bugfixes
3072
922 by Christophe Simonis
convert tabs to 4 spaces
3073
                            if f_pg_type != f_obj_type:
1398 by Fabien Pinckaers
improvements_bugfixes
3074
                                if not ok:
2445 by Fabien Pinckaers
[IMP] automated migration
3075
                                    i = 0
3076
                                    while True:
3017.2.12 by P. Christeas
ORM: moved columns should carry the old name, not the table
3077
                                        newname = k + '_moved' + str(i)
2445 by Fabien Pinckaers
[IMP] automated migration
3078
                                        cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \
3079
                                            "WHERE c.relname=%s " \
3080
                                            "AND a.attname=%s " \
3081
                                            "AND c.oid=a.attrelid ", (self._table, newname))
3082
                                        if not cr.fetchone()[0]:
3083
                                            break
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3084
                                        i += 1
2466 by Fabien Pinckaers
[IMP] removed price_accuracy options
3085
                                    if f_pg_notnull:
3086
                                        cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
2445 by Fabien Pinckaers
[IMP] automated migration
3087
                                    cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
3088
                                    cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
3619 by Olivier Dony
[FIX] orm: quote column names in queries to respect case and be consistent
3089
                                    cr.execute("COMMENT ON COLUMN %s.\"%s\" IS %%s" % (self._table, k), (f.string,))
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3090
                                    _schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3091
                                        self._table, k, f_pg_type, f._type, newname)
1398 by Fabien Pinckaers
improvements_bugfixes
3092
922 by Christophe Simonis
convert tabs to 4 spaces
3093
                            # if the field is required and hasn't got a NOT NULL constraint
3094
                            if f.required and f_pg_notnull == 0:
3095
                                # set the field to the default value if any
957 by Olivier Laurent
pep8
3096
                                if k in self._defaults:
1897 by Christophe Simonis
[IMP] _defaults: allow to pass directly the value instead of a lambda
3097
                                    if callable(self._defaults[k]):
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
3098
                                        default = self._defaults[k](self, cr, SUPERUSER_ID, context)
1897 by Christophe Simonis
[IMP] _defaults: allow to pass directly the value instead of a lambda
3099
                                    else:
3100
                                        default = self._defaults[k]
3101
4682.2.9 by Xavier Morel
[REM] unnecessary parens
3102
                                    if default is not None:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3103
                                        ss = self._columns[k]._symbol_set
1844.1.22 by uco(Open ERP)
[FIX] Allowing sql keywords as fields(don't use them in order by clause)
3104
                                        query = 'UPDATE "%s" SET "%s"=%s WHERE "%s" is NULL' % (self._table, k, ss[0], k)
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3105
                                        cr.execute(query, (ss[1](default),))
922 by Christophe Simonis
convert tabs to 4 spaces
3106
                                # add the NOT NULL constraint
1398 by Fabien Pinckaers
improvements_bugfixes
3107
                                cr.commit()
922 by Christophe Simonis
convert tabs to 4 spaces
3108
                                try:
3272 by Fabien Pinckaers
[IMP] logging for set null failed
3109
                                    cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
922 by Christophe Simonis
convert tabs to 4 spaces
3110
                                    cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3111
                                    _schema.debug("Table '%s': column '%s': added NOT NULL constraint",
3112
                                        self._table, k)
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
3113
                                except Exception:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3114
                                    msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
3115
                                        "If you want to have it, you should update the records and execute manually:\n"\
2707 by Xavier Morel
[IMP] logging in orm
3116
                                        "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3117
                                    _schema.warning(msg, self._table, k, self._table, k)
922 by Christophe Simonis
convert tabs to 4 spaces
3118
                                cr.commit()
3119
                            elif not f.required and f_pg_notnull == 1:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3120
                                cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
922 by Christophe Simonis
convert tabs to 4 spaces
3121
                                cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3122
                                _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
3123
                                    self._table, k)
2651 by Olivier Dony
[IMP] ir_translate, orm: btree indexes have size limit on large text values, implemented temporary workaround
3124
                            # Verify index
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3125
                            indexname = '%s_%s_index' % (self._table, k)
3126
                            cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = %s and tablename = %s", (indexname, self._table))
2445 by Fabien Pinckaers
[IMP] automated migration
3127
                            res2 = cr.dictfetchall()
3128
                            if not res2 and f.select:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3129
                                cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
922 by Christophe Simonis
convert tabs to 4 spaces
3130
                                cr.commit()
2651 by Olivier Dony
[IMP] ir_translate, orm: btree indexes have size limit on large text values, implemented temporary workaround
3131
                                if f._type == 'text':
3132
                                    # FIXME: for fields.text columns we should try creating GIN indexes instead (seems most suitable for an ERP context)
4445 by olt at tinyerp
[FIX] fixed message format (wrong number of arguments)
3133
                                    msg = "Table '%s': Adding (b-tree) index for %s column '%s'."\
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3134
                                        "This is probably useless (does not work for fulltext search) and prevents INSERTs of long texts"\
3135
                                        " because there is a length limit for indexable btree values!\n"\
2707 by Xavier Morel
[IMP] logging in orm
3136
                                        "Use a search view instead if you simply want to make the field searchable."
4445 by olt at tinyerp
[FIX] fixed message format (wrong number of arguments)
3137
                                    _schema.warning(msg, self._table, f._type, k)
2445 by Fabien Pinckaers
[IMP] automated migration
3138
                            if res2 and not f.select:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3139
                                cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
922 by Christophe Simonis
convert tabs to 4 spaces
3140
                                cr.commit()
2707 by Xavier Morel
[IMP] logging in orm
3141
                                msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3142
                                _schema.debug(msg, self._table, k, f._type)
2651 by Olivier Dony
[IMP] ir_translate, orm: btree indexes have size limit on large text values, implemented temporary workaround
3143
922 by Christophe Simonis
convert tabs to 4 spaces
3144
                            if isinstance(f, fields.many2one):
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
3145
                                dest_model = self.pool.get(f._obj)
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
3146
                                if dest_model._table != 'ir_actions':
3147
                                    self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
3148
3149
                    # The field doesn't exist in database. Create it if necessary.
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3150
                    else:
2445 by Fabien Pinckaers
[IMP] automated migration
3151
                        if not isinstance(f, fields.function) or f.store:
3152
                            # add the missing field
3153
                            cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
3619 by Olivier Dony
[FIX] orm: quote column names in queries to respect case and be consistent
3154
                            cr.execute("COMMENT ON COLUMN %s.\"%s\" IS %%s" % (self._table, k), (f.string,))
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3155
                            _schema.debug("Table '%s': added column '%s' with definition=%s",
2707 by Xavier Morel
[IMP] logging in orm
3156
                                self._table, k, get_pg_type(f)[1])
2445 by Fabien Pinckaers
[IMP] automated migration
3157
3158
                            # initialize it
3159
                            if not create and k in self._defaults:
3160
                                if callable(self._defaults[k]):
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
3161
                                    default = self._defaults[k](self, cr, SUPERUSER_ID, context)
2445 by Fabien Pinckaers
[IMP] automated migration
3162
                                else:
3163
                                    default = self._defaults[k]
3164
3165
                                ss = self._columns[k]._symbol_set
3166
                                query = 'UPDATE "%s" SET "%s"=%s' % (self._table, k, ss[0])
3167
                                cr.execute(query, (ss[1](default),))
3168
                                cr.commit()
3976.1.16 by Vo Minh Thu
[IMP] openerp.loglevels: removed calls to openerp.loglevels.Logger().
3169
                                _logger.debug("Table '%s': setting default value of new column %s", self._table, k)
2445 by Fabien Pinckaers
[IMP] automated migration
3170
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
3171
                            # remember the functions to call for the stored fields
2445 by Fabien Pinckaers
[IMP] automated migration
3172
                            if isinstance(f, fields.function):
3173
                                order = 10
3453.2.4 by Vo Minh Thu
[REF] orm: added some comments.
3174
                                if f.store is not True: # i.e. if f.store is a dict
2445 by Fabien Pinckaers
[IMP] automated migration
3175
                                    order = f.store[f.store.keys()[0]][2]
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3176
                                todo_end.append((order, self._update_store, (f, k)))
2445 by Fabien Pinckaers
[IMP] automated migration
3177
3178
                            # and add constraints if needed
3179
                            if isinstance(f, fields.many2one):
3180
                                if not self.pool.get(f._obj):
4682.2.9 by Xavier Morel
[REM] unnecessary parens
3181
                                    raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
3182
                                dest_model = self.pool.get(f._obj)
3183
                                ref = dest_model._table
2445 by Fabien Pinckaers
[IMP] automated migration
3184
                                # ir_actions is inherited so foreign key doesn't work on it
3185
                                if ref != 'ir_actions':
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
3186
                                    self._m2o_add_foreign_key_checked(k, dest_model, f.ondelete)
2445 by Fabien Pinckaers
[IMP] automated migration
3187
                            if f.select:
3188
                                cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
3189
                            if f.required:
3190
                                try:
3191
                                    cr.commit()
3272 by Fabien Pinckaers
[IMP] logging for set null failed
3192
                                    cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3193
                                    _schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
2707 by Xavier Morel
[IMP] logging in orm
3194
                                        self._table, k)
2445 by Fabien Pinckaers
[IMP] automated migration
3195
                                except Exception:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3196
                                    msg = "WARNING: unable to set column %s of table %s not null !\n"\
3359.2.9 by Vo Minh Thu
[REF] renamed openerp-server.py to openerp-server.
3197
                                        "Try to re-run: openerp-server --update=module\n"\
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3198
                                        "If it doesn't work, update records and execute manually:\n"\
2707 by Xavier Morel
[IMP] logging in orm
3199
                                        "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3200
                                    _logger.warning(msg, k, self._table, self._table, k)
2445 by Fabien Pinckaers
[IMP] automated migration
3201
                            cr.commit()
1341.2.4 by Fabien Pinckaers
bugfixes
3202
922 by Christophe Simonis
convert tabs to 4 spaces
3203
        else:
2334.1.8 by Anup(OpenERP)
[IMP] SQL injection Refactored
3204
            cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
922 by Christophe Simonis
convert tabs to 4 spaces
3205
            create = not bool(cr.fetchone())
3206
2238.1.21 by Jay(Open ERP)
[FIX] orm: ensure the cursor is valid before computing parent_store values
3207
        cr.commit()     # start a new transaction
3208
4188 by Olivier Dony
[FIX] orm: remove unused `module` parameter when creating constraints
3209
        self._add_sql_constraints(cr)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3210
3211
        if create:
3212
            self._execute_sql(cr)
3213
3214
        if store_compute:
3215
            self._parent_store_compute(cr)
3216
            cr.commit()
3217
3218
        return todo_end
3219
3220
    def _auto_end(self, cr, context=None):
3221
        """ Create the foreign keys recorded by _auto_init. """
3222
        for t, k, r, d in self._foreign_keys:
3223
            cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
4178.1.1 by Vo Minh Thu
[IMP] uninstall: started test module, added new ir_model_constraint table.
3224
            self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f')
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3225
        cr.commit()
3226
        del self._foreign_keys
3227
3228
3229
    def _table_exist(self, cr):
3230
        cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
3231
        return cr.rowcount
3232
3233
3234
    def _create_table(self, cr):
4643.1.2 by Christophe CHAUVET
[IMP] Remove completly the OIDS, cause WITHOUT OIDS is activate by default
3235
        cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id))' % (self._table,))
3619 by Olivier Dony
[FIX] orm: quote column names in queries to respect case and be consistent
3236
        cr.execute(("COMMENT ON TABLE \"%s\" IS %%s" % self._table), (self._description,))
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3237
        _schema.debug("Table '%s': created", self._table)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3238
3239
3240
    def _parent_columns_exist(self, cr):
3241
        cr.execute("""SELECT c.relname
3242
            FROM pg_class c, pg_attribute a
3243
            WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
3244
            """, (self._table, 'parent_left'))
3245
        return cr.rowcount
3246
3247
3248
    def _create_parent_columns(self, cr):
3249
        cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
3250
        cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
3251
        if 'parent_left' not in self._columns:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3252
            _logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
3253
                          self._table)
3254
            _schema.debug("Table '%s': added column '%s' with definition=%s",
3255
                self._table, 'parent_left', 'INTEGER')
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3256
        elif not self._columns['parent_left'].select:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3257
            _logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
3258
                          self._table)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3259
        if 'parent_right' not in self._columns:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3260
            _logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
3261
                          self._table)
3262
            _schema.debug("Table '%s': added column '%s' with definition=%s",
3263
                self._table, 'parent_right', 'INTEGER')
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3264
        elif not self._columns['parent_right'].select:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3265
            _logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
3266
                          self._table)
4681 by Olivier Dony
[IMP] orm: do not warn about missing ondelete cascade rule if ondelete is restrict - that's safe as well
3267
        if self._columns[self._parent_name].ondelete not in ('cascade', 'restrict'):
3268
            _logger.error("The column %s on object %s must be set as ondelete='cascade' or 'restrict'",
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3269
                          self._parent_name, self._name)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3270
3271
        cr.commit()
3272
3273
3274
    def _add_log_columns(self, cr):
3647 by Olivier Dony
[FIX] orm,expression: sanity checks for domain terms, cleanup, tests
3275
        for field, field_def in LOG_ACCESS_COLUMNS.iteritems():
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3276
            cr.execute("""
3277
                SELECT c.relname
3278
                  FROM pg_class c, pg_attribute a
3279
                 WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
3647 by Olivier Dony
[FIX] orm,expression: sanity checks for domain terms, cleanup, tests
3280
                """, (self._table, field))
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3281
            if not cr.rowcount:
3647 by Olivier Dony
[FIX] orm,expression: sanity checks for domain terms, cleanup, tests
3282
                cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, field, field_def))
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3283
                cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3284
                _schema.debug("Table '%s': added column '%s' with definition=%s",
3285
                    self._table, field, field_def)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3286
3287
3288
    def _select_column_data(self, cr):
3737 by Vo Minh Thu
[IMP] orm: added small comment.
3289
        # attlen is the number of bytes necessary to represent the type when
3290
        # the type has a fixed size. If the type has a varying size attlen is
3291
        # -1 and atttypmod is the size limit + 4, or -1 if there is no limit.
4466.1.3 by Christophe Simonis
[FIX] orm: handle fields.char() with not size correctly
3292
        cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN (CASE WHEN a.atttypmod=-1 THEN 0 ELSE a.atttypmod-4 END) ELSE a.attlen END as size " \
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3293
           "FROM pg_class c,pg_attribute a,pg_type t " \
3294
           "WHERE c.relname=%s " \
3295
           "AND c.oid=a.attrelid " \
3296
           "AND a.atttypid=t.oid", (self._table,))
3297
        return dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
3298
3299
3300
    def _o2m_raise_on_missing_reference(self, cr, f):
3301
        # TODO this check should be a method on fields.one2many.
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
3302
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3303
        other = self.pool.get(f._obj)
3304
        if other:
3305
            # TODO the condition could use fields_get_keys().
3306
            if f._fields_id not in other._columns.keys():
3307
                if f._fields_id not in other._inherit_fields.keys():
4682.2.9 by Xavier Morel
[REM] unnecessary parens
3308
                    raise except_orm('Programming Error', "There is no reference field '%s' found for '%s'" % (f._fields_id, f._obj,))
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3309
3310
    def _m2m_raise_or_create_relation(self, cr, f):
3511.1.37 by Olivier Dony
[IMP] fields.m2m: support implicit generation of m2m SQL names
3311
        m2m_tbl, col1, col2 = f._sql_names(self)
4178.1.3 by Vo Minh Thu
[IMP] uninstall: use a dedicated table for many2many relationships instead of overlaoding ir.model.data.
3312
        self._save_relation_table(cr, m2m_tbl)
3511.1.37 by Olivier Dony
[IMP] fields.m2m: support implicit generation of m2m SQL names
3313
        cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3314
        if not cr.dictfetchall():
3315
            if not self.pool.get(f._obj):
4682.2.9 by Xavier Morel
[REM] unnecessary parens
3316
                raise except_orm('Programming Error', 'Many2Many destination model does not exist: `%s`' % (f._obj,))
3511.1.36 by Olivier Dony
[IMP] orm: enforce proper m2o ondelete rules between TransientModels and Models
3317
            dest_model = self.pool.get(f._obj)
3318
            ref = dest_model._table
4643.1.2 by Christophe CHAUVET
[IMP] Remove completly the OIDS, cause WITHOUT OIDS is activate by default
3319
            cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s"))' % (m2m_tbl, col1, col2, col1, col2))
3511.1.29 by Olivier Dony
[REM] orm: m2m._no_foreign_keys attribute removed, replaced by SQL check
3320
            # create foreign key references with ondelete=cascade, unless the targets are SQL views
3321
            cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
3322
            if not cr.fetchall():
3511.1.37 by Olivier Dony
[IMP] fields.m2m: support implicit generation of m2m SQL names
3323
                self._m2o_add_foreign_key_unchecked(m2m_tbl, col2, dest_model, 'cascade')
3511.1.29 by Olivier Dony
[REM] orm: m2m._no_foreign_keys attribute removed, replaced by SQL check
3324
            cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (self._table,))
3325
            if not cr.fetchall():
3511.1.37 by Olivier Dony
[IMP] fields.m2m: support implicit generation of m2m SQL names
3326
                self._m2o_add_foreign_key_unchecked(m2m_tbl, col1, self, 'cascade')
3511.1.29 by Olivier Dony
[REM] orm: m2m._no_foreign_keys attribute removed, replaced by SQL check
3327
3511.1.37 by Olivier Dony
[IMP] fields.m2m: support implicit generation of m2m SQL names
3328
            cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (m2m_tbl, col1, m2m_tbl, col1))
3329
            cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (m2m_tbl, col2, m2m_tbl, col2))
3330
            cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (m2m_tbl, self._table, ref))
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3331
            cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3332
            _schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3333
3334
4188 by Olivier Dony
[FIX] orm: remove unused `module` parameter when creating constraints
3335
    def _add_sql_constraints(self, cr):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3336
        """
3337
3338
        Modify this model's database table constraints so they match the one in
3339
        _sql_constraints.
3340
3341
        """
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
3342
        def unify_cons_text(txt):
3343
            return txt.lower().replace(', ',',').replace(' (','(')
3344
957 by Olivier Laurent
pep8
3345
        for (key, con, _) in self._sql_constraints:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3346
            conname = '%s_%s' % (self._table, key)
2687 by olt at tinyerp
[IMP] orm: now detect constraint definition changes in _auto_init (lately it only detected if the constraint name had changed)
3347
4178.1.2 by Vo Minh Thu
[IMP] uninstall: use dedicated table instead of overloading ir.model.data.
3348
            self._save_constraint(cr, conname, 'u')
2687 by olt at tinyerp
[IMP] orm: now detect constraint definition changes in _auto_init (lately it only detected if the constraint name had changed)
3349
            cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
3350
            existing_constraints = cr.dictfetchall()
3351
            sql_actions = {
3352
                'drop': {
3353
                    'execute': False,
3354
                    'query': 'ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (self._table, conname, ),
3355
                    'msg_ok': "Table '%s': dropped constraint '%s'. Reason: its definition changed from '%%s' to '%s'" % (
3356
                        self._table, conname, con),
3357
                    'msg_err': "Table '%s': unable to drop \'%s\' constraint !" % (self._table, con),
3358
                    'order': 1,
3359
                },
3360
                'add': {
3361
                    'execute': False,
3362
                    'query': 'ALTER TABLE "%s" ADD CONSTRAINT "%s" %s' % (self._table, conname, con,),
3363
                    'msg_ok': "Table '%s': added constraint '%s' with definition=%s" % (self._table, conname, con),
3364
                    '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" % (
3365
                        self._table, con),
3366
                    'order': 2,
3367
                },
3368
            }
3369
3370
            if not existing_constraints:
3371
                # constraint does not exists:
3372
                sql_actions['add']['execute'] = True
3373
                sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
4066.1.11 by Olivier Dony
[FIX] orm: model auto-init - better algorithm for detecting stale constraints during update
3374
            elif unify_cons_text(con) not in [unify_cons_text(item['condef']) for item in existing_constraints]:
2687 by olt at tinyerp
[IMP] orm: now detect constraint definition changes in _auto_init (lately it only detected if the constraint name had changed)
3375
                # constraint exists but its definition has changed:
3376
                sql_actions['drop']['execute'] = True
3377
                sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
3378
                sql_actions['add']['execute'] = True
3379
                sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
3380
3381
            # we need to add the constraint:
3382
            sql_actions = [item for item in sql_actions.values()]
3383
            sql_actions.sort(key=lambda x: x['order'])
3384
            for sql_action in [action for action in sql_actions if action['execute']]:
922 by Christophe Simonis
convert tabs to 4 spaces
3385
                try:
2687 by olt at tinyerp
[IMP] orm: now detect constraint definition changes in _auto_init (lately it only detected if the constraint name had changed)
3386
                    cr.execute(sql_action['query'])
922 by Christophe Simonis
convert tabs to 4 spaces
3387
                    cr.commit()
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3388
                    _schema.debug(sql_action['msg_ok'])
922 by Christophe Simonis
convert tabs to 4 spaces
3389
                except:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3390
                    _schema.warning(sql_action['msg_err'])
2238.1.21 by Jay(Open ERP)
[FIX] orm: ensure the cursor is valid before computing parent_store values
3391
                    cr.rollback()
922 by Christophe Simonis
convert tabs to 4 spaces
3392
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3393
3394
    def _execute_sql(self, cr):
3395
        """ Execute the SQL code from the _sql attribute (if any)."""
3396
        if hasattr(self, "_sql"):
3397
            for line in self._sql.split(';'):
3398
                line2 = line.replace('\n', '').strip()
3399
                if line2:
3400
                    cr.execute(line2)
3401
                    cr.commit()
3402
922 by Christophe Simonis
convert tabs to 4 spaces
3403
    #
3404
    # Update objects that uses this one to update their _inherits fields
3405
    #
2111.3.4 by RVO(OpenERP)
[IMP] security-rule-improvement
3406
922 by Christophe Simonis
convert tabs to 4 spaces
3407
    def _inherits_reload_src(self):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3408
        """ Recompute the _inherit_fields mapping on each _inherits'd child model."""
3458.1.1 by Vo Minh Thu
[REF] osv: moved osv_pool to modules/registry.
3409
        for obj in self.pool.models.values():
922 by Christophe Simonis
convert tabs to 4 spaces
3410
            if self._name in obj._inherits:
3411
                obj._inherits_reload()
3412
3466.1.8 by Vo Minh Thu
[REF] orm: add a column_info class to represent entries in _inherit_fields,
3413
922 by Christophe Simonis
convert tabs to 4 spaces
3414
    def _inherits_reload(self):
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3415
        """ Recompute the _inherit_fields mapping.
3416
3417
        This will also call itself on each inherits'd child model.
3418
3419
        """
922 by Christophe Simonis
convert tabs to 4 spaces
3420
        res = {}
3421
        for table in self._inherits:
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3422
            other = self.pool.get(table)
3423
            for col in other._columns.keys():
3549.4.1 by Naresh(OpenERP)
[FIX]:_inherits for multilevel inheritence
3424
                res[col] = (table, self._inherits[table], other._columns[col], table)
3453.2.3 by Vo Minh Thu
[REF] orm: brake _auto_init in shorter methods.
3425
            for col in other._inherit_fields.keys():
3549.4.1 by Naresh(OpenERP)
[FIX]:_inherits for multilevel inheritence
3426
                res[col] = (table, self._inherits[table], other._inherit_fields[col][2], other._inherit_fields[col][3])
957 by Olivier Laurent
pep8
3427
        self._inherit_fields = res
3466.1.8 by Vo Minh Thu
[REF] orm: add a column_info class to represent entries in _inherit_fields,
3428
        self._all_columns = self._get_column_infos()
922 by Christophe Simonis
convert tabs to 4 spaces
3429
        self._inherits_reload_src()
3430
3466.1.8 by Vo Minh Thu
[REF] orm: add a column_info class to represent entries in _inherit_fields,
3431
3432
    def _get_column_infos(self):
3433
        """Returns a dict mapping all fields names (direct fields and
3434
           inherited field via _inherits) to a ``column_info`` struct
3435
           giving detailed columns """
3436
        result = {}
3549.4.1 by Naresh(OpenERP)
[FIX]:_inherits for multilevel inheritence
3437
        for k, (parent, m2o, col, original_parent) in self._inherit_fields.iteritems():
3438
            result[k] = fields.column_info(k, col, parent, m2o, original_parent)
3466.1.8 by Vo Minh Thu
[REF] orm: add a column_info class to represent entries in _inherit_fields,
3439
        for k, col in self._columns.iteritems():
3440
            result[k] = fields.column_info(k, col)
3441
        return result
3442
3443
2609 by Olivier Dony
[IMP] orm/inherits: check that m2o fields implementing _inherits are marked required with ondelete cascade
3444
    def _inherits_check(self):
3445
        for table, field_name in self._inherits.items():
3446
            if field_name not in self._columns:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
3447
                _logger.info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.', field_name, self._name)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3448
                self._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
2609 by Olivier Dony
[IMP] orm/inherits: check that m2o fields implementing _inherits are marked required with ondelete cascade
3449
                                                             required=True, ondelete="cascade")
4681 by Olivier Dony
[IMP] orm: do not warn about missing ondelete cascade rule if ondelete is restrict - that's safe as well
3450
            elif not self._columns[field_name].required or self._columns[field_name].ondelete.lower() not in ("cascade", "restrict"):
3451
                _logger.warning('Field definition for _inherits reference "%s" in "%s" must be marked as "required" with ondelete="cascade" or "restrict", forcing it to required + cascade.', field_name, self._name)
2609 by Olivier Dony
[IMP] orm/inherits: check that m2o fields implementing _inherits are marked required with ondelete cascade
3452
                self._columns[field_name].required = True
3453
                self._columns[field_name].ondelete = "cascade"
3454
2416 by Fabien Pinckaers
merge
3455
    #def __getattr__(self, name):
3456
    #    """
3457
    #    Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent
3458
    #    (though inherits doesn't use Python inheritance).
3459
    #    Handles translating between local ids and remote ids.
3460
    #    Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance
3461
    #                 when you have inherits.
3462
    #    """
3463
    #    for model, field in self._inherits.iteritems():
3464
    #        proxy = self.pool.get(model)
3465
    #        if hasattr(proxy, name):
3466
    #            attribute = getattr(proxy, name)
3467
    #            if not hasattr(attribute, '__call__'):
3468
    #                return attribute
3469
    #            break
3470
    #    else:
3471
    #        return super(orm, self).__getattr__(name)
3472
3473
    #    def _proxy(cr, uid, ids, *args, **kwargs):
3474
    #        objects = self.browse(cr, uid, ids, kwargs.get('context', None))
3475
    #        lst = [obj[field].id for obj in objects if obj[field]]
3476
    #        return getattr(proxy, name)(cr, uid, lst, *args, **kwargs)
3477
3478
    #    return _proxy
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
3479
2335.1.2 by Stephane Wirtel
[FIX] Allow to call the methods defined in the _inherits objects
3480
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3481
    def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
3482
        """ Return the definition of each field.
3483
3484
        The returned value is a dictionary (indiced by field name) of
3485
        dictionaries. The _inherits'd fields are included. The string, help,
3486
        and selection (if present) attributes are translated.
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3487
3488
        :param cr: database cursor
3489
        :param user: current user id
4682.2.15 by Xavier Morel
[FIX] incorrect docstrings or docstring param names not matching actual param names
3490
        :param allfields: list of fields
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3491
        :param context: context arguments, like lang, time zone
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
3492
        :return: dictionary of field dictionaries, each one describing a field of the business object
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3493
        :raise AccessError: * if user has no create/write rights on the requested object
3494
3495
        """
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3496
        if context is None:
3497
            context = {}
3498
4431 by Antonin Bourguignon
[FIX] fields_get check_access_right
3499
        write_access = self.check_access_rights(cr, user, 'write', raise_exception=False) \
3500
            or self.check_access_rights(cr, user, 'create', raise_exception=False)
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3501
3502
        res = {}
3503
4761.2.43 by Quentin (OpenERP)
[REF] code review
3504
        translation_obj = self.pool.get('ir.translation')
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3505
        for parent in self._inherits:
3506
            res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
3507
3508
        for f, field in self._columns.iteritems():
4158.1.1 by Olivier Dony
[IMP] orm: support model-level @groups attribute for access restriction
3509
            if (allfields and f not in allfields) or \
3510
                (field.groups and not self.user_has_groups(cr, user, groups=field.groups, context=context)):
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3511
                continue
3512
3684 by Olivier Dony
[FIX] fields: m2m SQL names may be dynamic now, care for it in field_to_dict()
3513
            res[f] = fields.field_to_dict(self, cr, user, field, context=context)
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3514
3515
            if not write_access:
3516
                res[f]['readonly'] = True
3517
                res[f]['states'] = {}
3518
4425.1.1 by Olivier Dony
[IMP] translations: attempt to optimize out _get_source calls when there is not context lang
3519
            if 'lang' in context:
3520
                if 'string' in res[f]:
3521
                    res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context['lang'])
3522
                    if res_trans:
3523
                        res[f]['string'] = res_trans
3524
                if 'help' in res[f]:
3525
                    help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context['lang'])
3526
                    if help_trans:
3527
                        res[f]['help'] = help_trans
3528
                if 'selection' in res[f]:
3529
                    if isinstance(field.selection, (tuple, list)):
3530
                        sel = field.selection
3531
                        sel2 = []
3532
                        for key, val in sel:
3533
                            val2 = None
3534
                            if val:
3535
                                val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection',  context['lang'], val)
3536
                            sel2.append((key, val2 or val))
3537
                        res[f]['selection'] = sel2
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3538
3539
        return res
3540
4672.4.3 by Vo Minh Thu
[IMP] orm: check groups-based access rights on model fields in read() and write().
3541
    def check_field_access_rights(self, cr, user, operation, fields, context=None):
3542
        """
3543
        Check the user access rights on the given fields. This raises Access
3544
        Denied if the user does not have the rights. Otherwise it returns the
3545
        fields (as is if the fields is not falsy, or the readable/writable
3546
        fields if fields is falsy).
3547
        """
3548
        def p(field_name):
3549
            """Predicate to test if the user has access to the given field name."""
4672.4.5 by Vo Minh Thu
[FIX] check_field_access_rights: ignore nonexisting fields
3550
            # Ignore requested field if it doesn't exist. This is ugly but
3551
            # it seems to happen at least with 'name_alias' on res.partner.
3552
            if field_name not in self._all_columns:
3553
                return True
4672.4.3 by Vo Minh Thu
[IMP] orm: check groups-based access rights on model fields in read() and write().
3554
            field = self._all_columns[field_name].column
4743.1.46 by Christophe Simonis
[FIX] orm: SUPERUSER is not restricted by groups on fields
3555
            if user != SUPERUSER_ID and field.groups:
4672.4.3 by Vo Minh Thu
[IMP] orm: check groups-based access rights on model fields in read() and write().
3556
                return self.user_has_groups(cr, user, groups=field.groups, context=context)
3557
            else:
3558
                return True
3559
        if not fields:
3560
            fields = filter(p, self._all_columns.keys())
3561
        else:
3562
            filtered_fields = filter(lambda a: not p(a), fields)
3563
            if filtered_fields:
3564
                _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, user, self._name, ', '.join(filtered_fields))
4672.4.7 by Vo Minh Thu
[IMP] orm/acl: proper message instead of `TODO`.
3565
                raise except_orm(
3566
                    _('Access Denied'),
3567
                    _('The requested operation cannot be completed due to security restrictions. '
3568
                    'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
3569
                    (self._description, operation))
4672.4.3 by Vo Minh Thu
[IMP] orm: check groups-based access rights on model fields in read() and write().
3570
        return fields
3571
922 by Christophe Simonis
convert tabs to 4 spaces
3572
    def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
3573
        """ Read records with given ids with the given fields
3574
3575
        :param cr: database cursor
3576
        :param user: current user id
3577
        :param ids: id or list of the ids of the records to read
3578
        :param fields: optional list of field names to return (default: all fields would be returned)
3579
        :type fields: list (example ['field_name_1', ...])
3580
        :param context: optional context dictionary - it may contains keys for specifying certain options
3581
                        like ``context_lang``, ``context_tz`` to alter the results of the call.
3582
                        A special ``bin_size`` boolean flag may also be passed in the context to request the
3583
                        value of all fields.binary columns to be returned as the size of the binary instead of its
3584
                        contents. This can also be selectively overriden by passing a field-specific flag
3585
                        in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
3586
                        Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
3587
        :return: list of dictionaries((dictionary per record asked)) with requested field values
3588
        :rtype: [{‘name_of_the_field’: value, ...}, ...]
3589
        :raise AccessError: * if user has no read rights on the requested object
3590
                            * if user tries to bypass access rules for read on the requested object
3591
3592
        """
3593
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
3594
        self.check_access_rights(cr, user, 'read')
4672.4.3 by Vo Minh Thu
[IMP] orm: check groups-based access rights on model fields in read() and write().
3595
        fields = self.check_field_access_rights(cr, user, 'read', fields)
922 by Christophe Simonis
convert tabs to 4 spaces
3596
        if isinstance(ids, (int, long)):
3597
            select = [ids]
1119.1.191 by P. Christeas
[Fix] Tolerate ids that came as strings.
3598
        else:
1919.1.9 by Fabien Pinckaers
[FIX] select current compay on users
3599
            select = ids
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3600
        select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
957 by Olivier Laurent
pep8
3601
        result = self._read_flat(cr, user, select, fields, context, load)
2183.1.18 by Christophe Simonis
[FIX] orm: correct the cleaning of incorrect reference fields
3602
4815.1.9 by Vo Minh Thu
[REF] orm:
3603
        if isinstance(ids, (int, long)):
1341.1.10 by Christophe Simonis
[FIX] orm: searching an inexisting id (as integer) return False (patch from Activity Solutions)
3604
            return result and result[0] or False
922 by Christophe Simonis
convert tabs to 4 spaces
3605
        return result
3606
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3607
    def _read_flat(self, cr, user, ids, fields_to_read, context=None, load='_classic_read'):
922 by Christophe Simonis
convert tabs to 4 spaces
3608
        if not context:
957 by Olivier Laurent
pep8
3609
            context = {}
922 by Christophe Simonis
convert tabs to 4 spaces
3610
        if not ids:
3611
            return []
4682.2.2 by Xavier Morel
[IMP] compare to None by identity
3612
        if fields_to_read is None:
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3613
            fields_to_read = self._columns.keys()
922 by Christophe Simonis
convert tabs to 4 spaces
3614
2507 by Olivier Dony
[IMP] orm.read(): properly support ir.rules using fields inherited from _inherits + cleanup
3615
        # Construct a clause for the security rules.
3616
        # 'tables' hold the list of tables necessary for the SELECT including the ir.rule clauses,
3617
        # or will at least contain self._table.
3618
        rule_clause, rule_params, tables = self.pool.get('ir.rule').domain_get(cr, user, self._name, 'read', context=context)
3619
922 by Christophe Simonis
convert tabs to 4 spaces
3620
        # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
3621
        fields_pre = [f for f in fields_to_read if
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
3622
                           f == self.CONCURRENCY_CHECK_FIELD
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
3623
                        or (f in self._columns and getattr(self._columns[f], '_classic_write'))
3624
                     ] + self._inherits.values()
922 by Christophe Simonis
convert tabs to 4 spaces
3625
3626
        res = []
957 by Olivier Laurent
pep8
3627
        if len(fields_pre):
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3628
            def convert_field(f):
3619 by Olivier Dony
[FIX] orm: quote column names in queries to respect case and be consistent
3629
                f_qual = '%s."%s"' % (self._table, f) # need fully-qualified references in case len(tables) > 1
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3630
                if f in ('create_date', 'write_date'):
2507 by Olivier Dony
[IMP] orm.read(): properly support ir.rules using fields inherited from _inherits + cleanup
3631
                    return "date_trunc('second', %s) as %s" % (f_qual, f)
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
3632
                if f == self.CONCURRENCY_CHECK_FIELD:
3633
                    if self._log_access:
4066.1.3 by Olivier Dony
[FIX] Correct remaining SQL now() calls, must use UTC
3634
                        return "COALESCE(%s.write_date, %s.create_date, (now() at time zone 'UTC'))::timestamp AS %s" % (self._table, self._table, f,)
3635
                    return "(now() at time zone 'UTC')::timestamp AS %s" % (f,)
1122 by Christophe Simonis
get_binary_size is renamed bin_size and its behavior is reversed (False by default)
3636
                if isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2512 by Jay(Open ERP)
[FIX] ORM.read() : property of type binary should not crash due to quotes
3637
                    return 'length(%s) as "%s"' % (f_qual, f)
2507 by Olivier Dony
[IMP] orm.read(): properly support ir.rules using fields inherited from _inherits + cleanup
3638
                return f_qual
3639
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3640
            fields_pre2 = map(convert_field, fields_pre)
1844.4.91 by Christophe Simonis
[FIX] orm: bug when browsing _inherits object
3641
            order_by = self._parent_order or self._order
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3642
            select_fields = ','.join(fields_pre2 + ['%s.id' % self._table])
2507 by Olivier Dony
[IMP] orm.read(): properly support ir.rules using fields inherited from _inherits + cleanup
3643
            query = 'SELECT %s FROM %s WHERE %s.id IN %%s' % (select_fields, ','.join(tables), self._table)
3644
            if rule_clause:
3645
                query += " AND " + (' OR '.join(rule_clause))
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
3646
            query += " ORDER BY " + order_by
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
3647
            for sub_ids in cr.split_for_in_conditions(ids):
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3648
                cr.execute(query, [tuple(sub_ids)] + rule_params)
3649
                results = cr.dictfetchall()
3650
                result_ids = [x['id'] for x in results]
3651
                self._check_record_rules_result_count(cr, user, sub_ids, result_ids, 'read', context=context)
3652
                res.extend(results)
922 by Christophe Simonis
convert tabs to 4 spaces
3653
        else:
3654
            res = map(lambda x: {'id': x}, ids)
3655
4672.3.1 by Vo Minh Thu
[IMP] Reduce considerably the loading time of a new registry.
3656
        if context.get('lang'):
3657
            for f in fields_pre:
3658
                if f == self.CONCURRENCY_CHECK_FIELD:
3659
                    continue
3660
                if self._columns[f].translate:
3661
                    ids = [x['id'] for x in res]
3662
                    #TODO: optimize out of this loop
3663
                    res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context['lang'], ids)
3664
                    for r in res:
3665
                        r[f] = res_trans.get(r['id'], False) or r[f]
922 by Christophe Simonis
convert tabs to 4 spaces
3666
3667
        for table in self._inherits:
3668
            col = self._inherits[table]
3142 by Fabien Pinckaers
fix_lp_673696
3669
            cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
922 by Christophe Simonis
convert tabs to 4 spaces
3670
            if not cols:
3671
                continue
3672
            res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
3673
3674
            res3 = {}
3675
            for r in res2:
3676
                res3[r['id']] = r
3677
                del r['id']
3678
3679
            for record in res:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3680
                if not record[col]: # if the record is deleted from _inherits table?
1844.4.78 by Jay(Open ERP)
[FIX] _inherits table may delete the record which is parental to other object but not required.
3681
                    continue
922 by Christophe Simonis
convert tabs to 4 spaces
3682
                record.update(res3[record[col]])
1046 by Christophe Simonis
rebugfix
3683
                if col not in fields_to_read:
922 by Christophe Simonis
convert tabs to 4 spaces
3684
                    del record[col]
3685
3686
        # all fields which need to be post-processed by a simple function (symbol_get)
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3687
        fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read)
922 by Christophe Simonis
convert tabs to 4 spaces
3688
        if fields_post:
3689
            for r in res:
3690
                for f in fields_post:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
3691
                    r[f] = self._columns[f]._symbol_get(r[f])
2507 by Olivier Dony
[IMP] orm.read(): properly support ir.rules using fields inherited from _inherits + cleanup
3692
        ids = [x['id'] for x in res]
922 by Christophe Simonis
convert tabs to 4 spaces
3693
3694
        # all non inherited fields for which the attribute whose name is in load is False
1029 by Christophe Simonis
binary fields: the server send the size (instead of the data) unless it's explicitly asked by setting 'get_binary_size' to False in the context
3695
        fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
974 by Fabien Pinckaers
Speed Improvement:
3696
3697
        # Compute POST fields
3698
        todo = {}
922 by Christophe Simonis
convert tabs to 4 spaces
3699
        for f in fields_post:
974 by Fabien Pinckaers
Speed Improvement:
3700
            todo.setdefault(self._columns[f]._multi, [])
3701
            todo[self._columns[f]._multi].append(f)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3702
        for key, val in todo.items():
974 by Fabien Pinckaers
Speed Improvement:
3703
            if key:
3704
                res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
3424 by Vo Minh Thu
[IMP] orm: added an assert on the return value of function fields.
3705
                assert res2 is not None, \
3706
                    'The function field "%s" on the "%s" model returned None\n' \
3707
                    '(a dictionary was expected).' % (val[0], self._name)
1056 by Fabien Pinckaers
Bugfix in Kernel
3708
                for pos in val:
974 by Fabien Pinckaers
Speed Improvement:
3709
                    for record in res:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
3710
                        if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
2874.1.6 by Anup(OpenERP)
[FIX] _read_flat() method corrected for multi
3711
                        multi_fields = res2.get(record['id'],{})
3712
                        if multi_fields:
3713
                            record[pos] = multi_fields.get(pos,[])
974 by Fabien Pinckaers
Speed Improvement:
3714
            else:
3715
                for f in val:
3716
                    res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
3717
                    for record in res:
1845 by HDA(OpenERP)
[Merged]
3718
                        if res2:
3719
                            record[f] = res2[record['id']]
3720
                        else:
3721
                            record[f] = []
4313.1.1 by Thibault Delavallée
[IMP] orm: added a deprecated attribute on fields. If not False, is a string, and make the ORM print a warning telling the field is deprecated.
3722
3723
        # Warn about deprecated fields now that fields_pre and fields_post are computed
4313.1.7 by Thibault Delavallée
[FIX] orm deprecated: added a list cast because we may receive tuples (as seen on runbot).
3724
        # Explicitly use list() because we may receive tuples
3725
        for f in list(fields_pre) + list(fields_post):
4313.1.4 by Thibault Delavallée
[IMP] Removed unnecessary conditions.
3726
            field_column = self._all_columns.get(f) and self._all_columns.get(f).column
4313.1.1 by Thibault Delavallée
[IMP] orm: added a deprecated attribute on fields. If not False, is a string, and make the ORM print a warning telling the field is deprecated.
3727
            if field_column and field_column.deprecated:
3728
                _logger.warning('Field %s.%s is deprecated: %s', self._name, f, field_column.deprecated)
3729
922 by Christophe Simonis
convert tabs to 4 spaces
3730
        readonly = None
3731
        for vals in res:
3732
            for field in vals.copy():
3733
                fobj = None
3734
                if field in self._columns:
3735
                    fobj = self._columns[field]
3736
4815.1.9 by Vo Minh Thu
[REF] orm:
3737
                if fobj:
3738
                    groups = fobj.read
3739
                    if groups:
3740
                        edit = False
3741
                        for group in groups:
3742
                            module = group.split(".")[0]
3743
                            grp = group.split(".")[1]
3744
                            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",  \
3745
                                       (grp, module, 'res.groups', user))
3746
                            readonly = cr.fetchall()
3747
                            if readonly[0][0] >= 1:
3748
                                edit = True
3749
                                break
3750
                            elif readonly[0][0] == 0:
3751
                                edit = False
3752
                            else:
3753
                                edit = False
3754
3755
                        if not edit:
3756
                            if type(vals[field]) == type([]):
3757
                                vals[field] = []
3758
                            elif type(vals[field]) == type(0.0):
3759
                                vals[field] = 0
3760
                            elif type(vals[field]) == type(''):
3761
                                vals[field] = '=No Permission='
3762
                            else:
3763
                                vals[field] = False
3764
3765
                if vals[field] is None:
3766
                    vals[field] = False
3767
922 by Christophe Simonis
convert tabs to 4 spaces
3768
        return res
3769
3511.1.3 by Vo Minh Thu
[REF] osv: removed orm_memory, adapted the distinguished bits to osv_memory,
3770
    # TODO check READ access
922 by Christophe Simonis
convert tabs to 4 spaces
3771
    def perm_read(self, cr, user, ids, context=None, details=True):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3772
        """
2849 by Olivier Dony
[ADD] osv.perm_read: added XML ID (xmlid) to returned values - naming indicates this is not a m2o.
3773
        Returns some metadata about the given records.
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3774
2199 by Olivier Dony
[FIX] minor: escaped RST special char in docstring
3775
        :param details: if True, \*_uid fields are replaced with the name of the user
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
3776
        :return: list of ownership dictionaries for each requested record
3777
        :rtype: list of dictionaries with the following keys:
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3778
3779
                    * id: object id
3780
                    * create_uid: user who created the record
3781
                    * create_date: date when the record was created
3782
                    * write_uid: last user who changed the record
3783
                    * write_date: date of the last change to the record
2849 by Olivier Dony
[ADD] osv.perm_read: added XML ID (xmlid) to returned values - naming indicates this is not a m2o.
3784
                    * xmlid: XML ID to use to refer to this record (if there is one), in format ``module.name``
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3785
        """
922 by Christophe Simonis
convert tabs to 4 spaces
3786
        if not context:
957 by Olivier Laurent
pep8
3787
            context = {}
922 by Christophe Simonis
convert tabs to 4 spaces
3788
        if not ids:
3789
            return []
3790
        fields = ''
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
3791
        uniq = isinstance(ids, (int, long))
3792
        if uniq:
3793
            ids = [ids]
2849 by Olivier Dony
[ADD] osv.perm_read: added XML ID (xmlid) to returned values - naming indicates this is not a m2o.
3794
        fields = ['id']
922 by Christophe Simonis
convert tabs to 4 spaces
3795
        if self._log_access:
2849 by Olivier Dony
[ADD] osv.perm_read: added XML ID (xmlid) to returned values - naming indicates this is not a m2o.
3796
            fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
3797
        quoted_table = '"%s"' % self._table
3798
        fields_str = ",".join('%s.%s'%(quoted_table, field) for field in fields)
3799
        query = '''SELECT %s, __imd.module, __imd.name
3800
                   FROM %s LEFT JOIN ir_model_data __imd
3801
                       ON (__imd.model = %%s and __imd.res_id = %s.id)
3802
                   WHERE %s.id IN %%s''' % (fields_str, quoted_table, quoted_table, quoted_table)
3803
        cr.execute(query, (self._name, tuple(ids)))
922 by Christophe Simonis
convert tabs to 4 spaces
3804
        res = cr.dictfetchall()
3805
        for r in res:
3806
            for key in r:
3807
                r[key] = r[key] or False
3017.2.9 by P. Christeas
orm: Return numeric uid at perm_read() when no access to res.user
3808
                if details and key in ('write_uid', 'create_uid') and r[key]:
3809
                    try:
922 by Christophe Simonis
convert tabs to 4 spaces
3810
                        r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
3017.2.9 by P. Christeas
orm: Return numeric uid at perm_read() when no access to res.user
3811
                    except Exception:
3812
                        pass # Leave the numeric uid there
2849 by Olivier Dony
[ADD] osv.perm_read: added XML ID (xmlid) to returned values - naming indicates this is not a m2o.
3813
            r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False
3814
            del r['name'], r['module']
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
3815
        if uniq:
3816
            return res[ids[0]]
922 by Christophe Simonis
convert tabs to 4 spaces
3817
        return res
3818
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
3819
    def _check_concurrency(self, cr, ids, context):
3820
        if not context:
3821
            return
2940 by Fabien Pinckaers
fix bug introduced in r2938
3822
        if not (context.get(self.CONCURRENCY_CHECK_FIELD) and self._log_access):
2938 by Xavier Morel
[IMP] cleanup _check_concurrency a bit and reduce nesting
3823
            return
4066.1.3 by Olivier Dony
[FIX] Correct remaining SQL now() calls, must use UTC
3824
        check_clause = "(id = %s AND %s < COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp)"
2946 by Olivier Dony
[FIX] orm._check_concurrency: pop values from context after check to avoid false positives (e.g. nested writes) - one check per transaction is enough
3825
        for sub_ids in cr.split_for_in_conditions(ids):
3826
            ids_to_check = []
3827
            for id in sub_ids:
3828
                id_ref = "%s,%s" % (self._name, id)
3829
                update_date = context[self.CONCURRENCY_CHECK_FIELD].pop(id_ref, None)
3830
                if update_date:
3831
                    ids_to_check.extend([id, update_date])
3832
            if not ids_to_check:
3833
                continue
2947 by Olivier Dony
[FIX] orm._check_concurrency: mention one conflicting record in exception message
3834
            cr.execute("SELECT id FROM %s WHERE %s" % (self._table, " OR ".join([check_clause]*(len(ids_to_check)/2))), tuple(ids_to_check))
3835
            res = cr.fetchone()
3836
            if res:
3837
                # mention the first one only to keep the error message readable
3838
                raise except_orm('ConcurrencyException', _('A document was modified since you last viewed it (%s:%d)') % (self._description, res[0]))
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
3839
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3840
    def _check_record_rules_result_count(self, cr, uid, ids, result_ids, operation, context=None):
3841
        """Verify the returned rows after applying record rules matches
4199.1.2 by Olivier Dony
[IMP] access rights: improve error messages for ACLs and record rules
3842
           the length of `ids`, and raise an appropriate exception if it does not.
3843
        """
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3844
        ids, result_ids = set(ids), set(result_ids)
3845
        missing_ids = ids - result_ids
3846
        if missing_ids:
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
3847
            # Attempt to distinguish record rule restriction vs deleted records,
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3848
            # to provide a more specific error message - check if the missinf
3849
            cr.execute('SELECT id FROM ' + self._table + ' WHERE id IN %s', (tuple(missing_ids),))
3850
            if cr.rowcount:
3851
                # the missing ids are (at least partially) hidden by access rules
4554 by Olivier Dony
[FIX] orm: make sure superuser mode always bypasses access rules filtering - one case was not covered after recent refactoring
3852
                if uid == SUPERUSER_ID:
3853
                    return
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3854
                _logger.warning('Access Denied by record rules for operation: %s, uid: %s, model: %s', operation, uid, self._name)
3855
                raise except_orm(_('Access Denied'),
3856
                                 _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
3857
                                    (self._description, operation))
3858
            else:
3859
                # If we get here, the missing_ids are not in the database
3860
                if operation in ('read','unlink'):
3861
                    # No need to warn about deleting an already deleted record.
3862
                    # And no error when reading a record that was deleted, to prevent spurious
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
3863
                    # errors for non-transactional search/read sequences coming from clients
4199.1.2 by Olivier Dony
[IMP] access rights: improve error messages for ACLs and record rules
3864
                    return
3865
                _logger.warning('Failed operation on deleted record(s): %s, uid: %s, model: %s', operation, uid, self._name)
3866
                raise except_orm(_('Missing document(s)'),
3867
                                 _('One of the documents you are trying to access has been deleted, please try again after refreshing.'))
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3868
4199.1.2 by Olivier Dony
[IMP] access rights: improve error messages for ACLs and record rules
3869
3668 by Vo Minh Thu
[IMP] orm: expose ir.model.access check shortcuts.
3870
    def check_access_rights(self, cr, uid, operation, raise_exception=True): # no context on purpose.
3871
        """Verifies that the operation given by ``operation`` is allowed for the user
3872
           according to the access rights."""
3873
        return self.pool.get('ir.model.access').check(cr, uid, self._name, operation, raise_exception)
3874
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
3875
    def check_access_rule(self, cr, uid, ids, operation, context=None):
3876
        """Verifies that the operation given by ``operation`` is allowed for the user
3877
           according to ir.rules.
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
3878
3879
           :param operation: one of ``write``, ``unlink``
2111.3.20 by HDA(OpenERP)
Improved docstring of orm
3880
           :raise except_orm: * if current ir.rules do not permit this operation.
3881
           :return: None if the operation is allowed
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
3882
        """
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
3883
        if uid == SUPERUSER_ID:
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
3884
            return
3885
3718 by Vo Minh Thu
[FIX] orm: a typo made all models treated as if they were transient in check_access_rule().
3886
        if self.is_transient():
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
3887
            # Only one single implicit access rule for transient models: owner only!
3888
            # This is ok to hardcode because we assert that TransientModels always
4199.1.2 by Olivier Dony
[IMP] access rights: improve error messages for ACLs and record rules
3889
            # have log_access enabled so that the create_uid column is always there.
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
3890
            # And even with _inherits, these fields are always present in the local
3891
            # table too, so no need for JOINs.
3892
            cr.execute("""SELECT distinct create_uid
3893
                          FROM %s
3894
                          WHERE id IN %%s""" % self._table, (tuple(ids),))
3895
            uids = [x[0] for x in cr.fetchall()]
3896
            if len(uids) != 1 or uids[0] != uid:
4199.1.2 by Olivier Dony
[IMP] access rights: improve error messages for ACLs and record rules
3897
                raise except_orm(_('Access Denied'),
3898
                                 _('For this kind of document, you may only access records you created yourself.\n\n(Document type: %s)') % (self._description,))
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
3899
        else:
3900
            where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3901
            if where_clause:
3902
                where_clause = ' and ' + ' and '.join(where_clause)
3903
                for sub_ids in cr.split_for_in_conditions(ids):
3904
                    cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3905
                               ' WHERE ' + self._table + '.id IN %s' + where_clause,
3906
                               [sub_ids] + where_params)
4549.1.2 by Olivier Dony
[IMP] orm: make error handling more consistent when accessing deleted/filtered records
3907
                    returned_ids = [x['id'] for x in cr.dictfetchall()]
3908
                    self._check_record_rules_result_count(cr, uid, sub_ids, returned_ids, operation, context=context)
2111.3.4 by RVO(OpenERP)
[IMP] security-rule-improvement
3909
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3910
    def create_workflow(self, cr, uid, ids, context=None):
4769.2.5 by Vo Minh Thu
[DOC] orm: documented workflow-related methods.
3911
        """Create a workflow instance for each given record IDs."""
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3912
        from openerp import workflow
4769.2.4 by Vo Minh Thu
[IMP] added create_workflow, delete_workflow,redirect_workflow as ORM methods.
3913
        for res_id in ids:
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3914
            workflow.trg_create(uid, self._name, res_id, cr)
4769.2.4 by Vo Minh Thu
[IMP] added create_workflow, delete_workflow,redirect_workflow as ORM methods.
3915
        return True
3916
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3917
    def delete_workflow(self, cr, uid, ids, context=None):
4769.2.5 by Vo Minh Thu
[DOC] orm: documented workflow-related methods.
3918
        """Delete the workflow instances bound to the given record IDs."""
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3919
        from openerp import workflow
3920
        for res_id in ids:
3921
            workflow.trg_delete(uid, self._name, res_id, cr)
3922
        return True
3923
4769.2.9 by Raphael Collet
[IMP] rename model method 'trigger_workflow' into 'step_workflow' (less confusing)
3924
    def step_workflow(self, cr, uid, ids, context=None):
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3925
        """Reevaluate the workflow instances of the given record IDs."""
3926
        from openerp import workflow
3927
        for res_id in ids:
3928
            workflow.trg_write(uid, self._name, res_id, cr)
3929
        return True
3930
3931
    def signal_workflow(self, cr, uid, ids, signal, context=None):
3932
        """Send given workflow signal and return a dict mapping ids to workflow results"""
3933
        from openerp import workflow
3934
        result = {}
3935
        for res_id in ids:
3936
            result[res_id] = workflow.trg_validate(uid, self._name, res_id, signal, cr)
3937
        return result
3938
3939
    def redirect_workflow(self, cr, uid, old_new_ids, context=None):
3940
        """ Rebind the workflow instance bound to the given 'old' record IDs to
3941
            the given 'new' IDs. (``old_new_ids`` is a list of pairs ``(old, new)``.
3942
        """
3943
        from openerp import workflow
4769.2.4 by Vo Minh Thu
[IMP] added create_workflow, delete_workflow,redirect_workflow as ORM methods.
3944
        for old_id, new_id in old_new_ids:
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3945
            workflow.trg_redirect(uid, self._name, old_id, new_id, cr)
4769.2.4 by Vo Minh Thu
[IMP] added create_workflow, delete_workflow,redirect_workflow as ORM methods.
3946
        return True
3947
922 by Christophe Simonis
convert tabs to 4 spaces
3948
    def unlink(self, cr, uid, ids, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3949
        """
3950
        Delete records with given ids
3951
3952
        :param cr: database cursor
3953
        :param uid: current user id
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
3954
        :param ids: id or list of ids
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
3955
        :param context: (optional) context arguments, like lang, time zone
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
3956
        :return: True
3957
        :raise AccessError: * if user has no unlink rights on the requested object
3958
                            * if user tries to bypass access rules for unlink on the requested object
3959
        :raise UserError: if the record is default property for other records
3960
3961
        """
922 by Christophe Simonis
convert tabs to 4 spaces
3962
        if not ids:
3963
            return True
3964
        if isinstance(ids, (int, long)):
3965
            ids = [ids]
3966
4066.26.2 by Xavier ALT
[FIX] OPW 577292: orm: force computing store values for all columns on unlink()
3967
        result_store = self._store_get_values(cr, uid, ids, self._all_columns.keys(), context)
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
3968
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
3969
        self._check_concurrency(cr, ids, context)
922 by Christophe Simonis
convert tabs to 4 spaces
3970
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
3971
        self.check_access_rights(cr, uid, 'unlink')
922 by Christophe Simonis
convert tabs to 4 spaces
3972
3846.2.2 by Vo Minh Thu
[IMP] orm: cosmetics (and the runbot will build this branch again).
3973
        ir_property = self.pool.get('ir.property')
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
3974
3846.2.2 by Vo Minh Thu
[IMP] orm: cosmetics (and the runbot will build this branch again).
3975
        # Check if the records are used as default properties.
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
3976
        domain = [('res_id', '=', False),
2236 by Quality Team
[IMP] Add the capabilities to store a datetime, date, integer, float and char in a field.property
3977
                  ('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
1803.1.36 by Christophe Simonis
[FIX] disallow the deletion of records set as default properties
3978
                 ]
3846.2.2 by Vo Minh Thu
[IMP] orm: cosmetics (and the runbot will build this branch again).
3979
        if ir_property.search(cr, uid, domain, context=context):
1803.1.36 by Christophe Simonis
[FIX] disallow the deletion of records set as default properties
3980
            raise except_orm(_('Error'), _('Unable to delete this document because it is used as a default property'))
3981
3846.2.2 by Vo Minh Thu
[IMP] orm: cosmetics (and the runbot will build this branch again).
3982
        # Delete the records' properties.
3983
        property_ids = ir_property.search(cr, uid, [('res_id', 'in', ['%s,%s' % (self._name, i) for i in ids])], context=context)
3984
        ir_property.unlink(cr, uid, property_ids, context=context)
3985
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
3986
        self.delete_workflow(cr, uid, ids, context=context)
922 by Christophe Simonis
convert tabs to 4 spaces
3987
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
3988
        self.check_access_rule(cr, uid, ids, 'unlink', context=context)
3310.10.1 by Jay Vora(OpenERP)
[FIX] Deletion : Deletion of the records which are referring to any xml/csv record,should delete the relevant reference of ir_model_data and ir_values (Ref : Case 4630)
3989
        pool_model_data = self.pool.get('ir.model.data')
3416 by Vo Minh Thu
[REF] consistent, grepable ir.values object naming.
3990
        ir_values_obj = self.pool.get('ir.values')
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
3991
        for sub_ids in cr.split_for_in_conditions(ids):
3992
            cr.execute('delete from ' + self._table + ' ' \
2334.1.9 by vra
[FIX][IMP] SQL-Injection ,minor improvement.
3993
                       'where id IN %s', (sub_ids,))
3310.10.4 by Olivier Dony
[FIX] orm.unlink(): properly remove ir.values entries upon deletion
3994
3995
            # Removing the ir_model_data reference if the record being deleted is a record created by xml/csv file,
3996
            # as these are not connected with real database foreign keys, and would be dangling references.
3480 by Olivier Dony
[FIX] orm.unlink(): cascade deletion of ir.model.data performed as admin
3997
            # Note: following steps performed as admin to avoid access rights restrictions, and with no context
3998
            #       to avoid possible side-effects during admin calls.
3999
            # Step 1. Calling unlink of ir_model_data only for the affected IDS
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
4000
            reference_ids = pool_model_data.search(cr, SUPERUSER_ID, [('res_id','in',list(sub_ids)),('model','=',self._name)])
3310.10.1 by Jay Vora(OpenERP)
[FIX] Deletion : Deletion of the records which are referring to any xml/csv record,should delete the relevant reference of ir_model_data and ir_values (Ref : Case 4630)
4001
            # Step 2. Marching towards the real deletion of referenced records
3407.1.4 by Olivier Dony
[IMP] orm: minor typo and improvement in handling of ir.model.data during unlink()
4002
            if reference_ids:
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
4003
                pool_model_data.unlink(cr, SUPERUSER_ID, reference_ids)
3310.10.4 by Olivier Dony
[FIX] orm.unlink(): properly remove ir.values entries upon deletion
4004
3310.10.1 by Jay Vora(OpenERP)
[FIX] Deletion : Deletion of the records which are referring to any xml/csv record,should delete the relevant reference of ir_model_data and ir_values (Ref : Case 4630)
4005
            # For the same reason, removing the record relevant to ir_values
3416 by Vo Minh Thu
[REF] consistent, grepable ir.values object naming.
4006
            ir_value_ids = ir_values_obj.search(cr, uid,
3310.11.1 by Jay Vora(OpenERP)
[FIX] Deletion : Deletion of the records which are referring to any xml/csv record,should delete the relevant reference of ir_model_data and ir_values (Ref : Case 4630)
4007
                    ['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
3310.10.4 by Olivier Dony
[FIX] orm.unlink(): properly remove ir.values entries upon deletion
4008
                    context=context)
3310.10.1 by Jay Vora(OpenERP)
[FIX] Deletion : Deletion of the records which are referring to any xml/csv record,should delete the relevant reference of ir_model_data and ir_values (Ref : Case 4630)
4009
            if ir_value_ids:
3416 by Vo Minh Thu
[REF] consistent, grepable ir.values object naming.
4010
                ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
3310.10.4 by Olivier Dony
[FIX] orm.unlink(): properly remove ir.values entries upon deletion
4011
1803.1.36 by Christophe Simonis
[FIX] disallow the deletion of records set as default properties
4012
        for order, object, store_ids, fields in result_store:
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
4013
            if object != self._name:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4014
                obj = self.pool.get(object)
4015
                cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
1803.1.36 by Christophe Simonis
[FIX] disallow the deletion of records set as default properties
4016
                rids = map(lambda x: x[0], cr.fetchall())
4017
                if rids:
4018
                    obj._store_set_values(cr, uid, rids, fields, context)
3310.10.4 by Olivier Dony
[FIX] orm.unlink(): properly remove ir.values entries upon deletion
4019
922 by Christophe Simonis
convert tabs to 4 spaces
4020
        return True
4021
4022
    #
4023
    # TODO: Validate
4024
    #
4025
    def write(self, cr, user, ids, vals, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4026
        """
4027
        Update records with given ids with the given field values
4028
4029
        :param cr: database cursor
4030
        :param user: current user id
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4031
        :type user: integer
4032
        :param ids: object id or list of object ids to update according to **vals**
4033
        :param vals: field values to update, e.g {'field_name': new_field_value, ...}
4034
        :type vals: dictionary
4035
        :param context: (optional) context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
4036
        :type context: dictionary
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4037
        :return: True
4038
        :raise AccessError: * if user has no write rights on the requested object
4039
                            * if user tries to bypass access rules for write on the requested object
4040
        :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4041
        :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)
4042
4043
        **Note**: The type of field values to pass in ``vals`` for relationship fields is specific:
4044
4045
            + For a many2many field, a list of tuples is expected.
4046
              Here is the list of tuple that are accepted, with the corresponding semantics ::
4047
4048
                 (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
4049
                 (1, ID, { values })    update the linked record with id = ID (write *values* on it)
4050
                 (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)
4051
                 (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)
4052
                 (4, ID)                link to existing record with id = ID (adds a relationship)
4053
                 (5)                    unlink all (like using (3,ID) for all linked records)
4054
                 (6, 0, [IDs])          replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
4055
4056
                 Example:
4057
                    [(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
4058
4059
            + For a one2many field, a lits of tuples is expected.
4060
              Here is the list of tuple that are accepted, with the corresponding semantics ::
4061
4062
                 (0, 0,  { values })    link to a new record that needs to be created with the given values dictionary
4063
                 (1, ID, { values })    update the linked record with id = ID (write *values* on it)
4064
                 (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)
4065
4066
                 Example:
4067
                    [(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
4068
4069
            + For a many2one field, simply use the ID of target record, which must already exist, or ``False`` to remove the link.
4070
            + For a reference field, use a string with the model name, a comma, and the target object id (example: ``'product.product, 5'``)
2383 by Antony Lesuisse
improve write to many2many documentation
4071
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4072
        """
922 by Christophe Simonis
convert tabs to 4 spaces
4073
        readonly = None
4672.4.3 by Vo Minh Thu
[IMP] orm: check groups-based access rights on model fields in read() and write().
4074
        self.check_field_access_rights(cr, user, 'write', vals.keys())
922 by Christophe Simonis
convert tabs to 4 spaces
4075
        for field in vals.copy():
4076
            fobj = None
4077
            if field in self._columns:
4078
                fobj = self._columns[field]
3017.1.23 by Olivier Dony
[IMP] orm.write: ignore unknown fields, more consistent with rest of API
4079
            elif field in self._inherit_fields:
922 by Christophe Simonis
convert tabs to 4 spaces
4080
                fobj = self._inherit_fields[field][2]
4081
            if not fobj:
4082
                continue
4083
            groups = fobj.write
4084
4085
            if groups:
4086
                edit = False
4087
                for group in groups:
4088
                    module = group.split(".")[0]
4089
                    grp = group.split(".")[1]
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
4090
                    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", \
922 by Christophe Simonis
convert tabs to 4 spaces
4091
                               (grp, module, 'res.groups', user))
4092
                    readonly = cr.fetchall()
4093
                    if readonly[0][0] >= 1:
4094
                        edit = True
4095
                        break
4096
4097
                if not edit:
4098
                    vals.pop(field)
4099
4100
        if not context:
957 by Olivier Laurent
pep8
4101
            context = {}
922 by Christophe Simonis
convert tabs to 4 spaces
4102
        if not ids:
4103
            return True
4104
        if isinstance(ids, (int, long)):
4105
            ids = [ids]
1589.1.1 by Christophe Simonis
[FIX] fix concurrency problem. Use last_modified instead of delta. Thanks to Tryton team for the idea.
4106
4107
        self._check_concurrency(cr, ids, context)
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
4108
        self.check_access_rights(cr, user, 'write')
922 by Christophe Simonis
convert tabs to 4 spaces
4109
2343 by Fabien Pinckaers
[FIX] store fields
4110
        result = self._store_get_values(cr, user, ids, vals.keys(), context) or []
4111
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4112
        # No direct update of parent_left/right
4113
        vals.pop('parent_left', None)
4114
        vals.pop('parent_right', None)
4115
4116
        parents_changed = []
3347 by Olivier Dony
[FIX] orm.write: parent_store updates must be processed in correct order to avoid edge cases
4117
        parent_order = self._parent_order or self._order
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4118
        if self._parent_store and (self._parent_name in vals):
4119
            # The parent_left/right computation may take up to
4120
            # 5 seconds. No need to recompute the values if the
3347 by Olivier Dony
[FIX] orm.write: parent_store updates must be processed in correct order to avoid edge cases
4121
            # parent is the same.
4122
            # Note: to respect parent_order, nodes must be processed in
4123
            # order, so ``parents_changed`` must be ordered properly.
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4124
            parent_val = vals[self._parent_name]
4125
            if parent_val:
3347 by Olivier Dony
[FIX] orm.write: parent_store updates must be processed in correct order to avoid edge cases
4126
                query = "SELECT id FROM %s WHERE id IN %%s AND (%s != %%s OR %s IS NULL) ORDER BY %s" % \
4127
                                (self._table, self._parent_name, self._parent_name, parent_order)
2397 by Borja López Soilán (Pexego)
[MERGE] orm: port of 5.0 fix for parent_store computation when previous parent is NULL
4128
                cr.execute(query, (tuple(ids), parent_val))
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4129
            else:
3347 by Olivier Dony
[FIX] orm.write: parent_store updates must be processed in correct order to avoid edge cases
4130
                query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \
4131
                                (self._table, self._parent_name, parent_order)
2397 by Borja López Soilán (Pexego)
[MERGE] orm: port of 5.0 fix for parent_store computation when previous parent is NULL
4132
                cr.execute(query, (tuple(ids),))
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4133
            parents_changed = map(operator.itemgetter(0), cr.fetchall())
1828 by Fabien Pinckaers
[IMP] Speed impprovement: 2x faster for flow: sale -> invoice -> payment
4134
957 by Olivier Laurent
pep8
4135
        upd0 = []
4136
        upd1 = []
4137
        upd_todo = []
4138
        updend = []
922 by Christophe Simonis
convert tabs to 4 spaces
4139
        direct = []
4140
        totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
4141
        for field in vals:
4313.1.4 by Thibault Delavallée
[IMP] Removed unnecessary conditions.
4142
            field_column = self._all_columns.get(field) and self._all_columns.get(field).column
4313.1.1 by Thibault Delavallée
[IMP] orm: added a deprecated attribute on fields. If not False, is a string, and make the ORM print a warning telling the field is deprecated.
4143
            if field_column and field_column.deprecated:
4144
                _logger.warning('Field %s.%s is deprecated: %s', self._name, field, field_column.deprecated)
922 by Christophe Simonis
convert tabs to 4 spaces
4145
            if field in self._columns:
1541 by Fabien Pinckaers
bugfix
4146
                if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
922 by Christophe Simonis
convert tabs to 4 spaces
4147
                    if (not totranslate) or not self._columns[field].translate:
4148
                        upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0])
4149
                        upd1.append(self._columns[field]._symbol_set[1](vals[field]))
4150
                    direct.append(field)
4151
                else:
4152
                    upd_todo.append(field)
4153
            else:
4154
                updend.append(field)
4155
            if field in self._columns \
4156
                    and hasattr(self._columns[field], 'selection') \
4157
                    and vals[field]:
3129.1.2 by Olivier Dony
[FIX] fields.reference: don't accept half-defined references, and avoid crashing on previous bad ones
4158
                self._check_selection_field_value(cr, user, field, vals[field], context=context)
922 by Christophe Simonis
convert tabs to 4 spaces
4159
4160
        if self._log_access:
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
4161
            upd0.append('write_uid=%s')
4066.1.3 by Olivier Dony
[FIX] Correct remaining SQL now() calls, must use UTC
4162
            upd0.append("write_date=(now() at time zone 'UTC')")
922 by Christophe Simonis
convert tabs to 4 spaces
4163
            upd1.append(user)
4164
4165
        if len(upd0):
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
4166
            self.check_access_rule(cr, user, ids, 'write', context=context)
4167
            for sub_ids in cr.split_for_in_conditions(ids):
4168
                cr.execute('update ' + self._table + ' set ' + ','.join(upd0) + ' ' \
2334.1.9 by vra
[FIX][IMP] SQL-Injection ,minor improvement.
4169
                           'where id IN %s', upd1 + [sub_ids])
2954 by Olivier Dony
[IMP] orm.write: report an error when trying to update deleted records (thanks RGA for initial fix)
4170
                if cr.rowcount != len(sub_ids):
4171
                    raise except_orm(_('AccessError'),
4172
                                     _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description)
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4173
922 by Christophe Simonis
convert tabs to 4 spaces
4174
            if totranslate:
1884.1.4 by Harry (Open ERP)
[FIX] replaced <TAB> with four white space.
4175
                # TODO: optimize
922 by Christophe Simonis
convert tabs to 4 spaces
4176
                for f in direct:
4177
                    if self._columns[f].translate:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4178
                        src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
1844.4.65 by ACH,JVO
[FIX] Translation was not getting inserted to DB on write()
4179
                        if not src_trans:
4180
                            src_trans = vals[f]
4181
                            # Inserting value to DB
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4182
                            self.write(cr, user, ids, {f: vals[f]})
1844.4.65 by ACH,JVO
[FIX] Translation was not getting inserted to DB on write()
4183
                        self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
922 by Christophe Simonis
convert tabs to 4 spaces
4184
1828 by Fabien Pinckaers
[IMP] Speed impprovement: 2x faster for flow: sale -> invoice -> payment
4185
922 by Christophe Simonis
convert tabs to 4 spaces
4186
        # call the 'set' method of fields which are not classic_write
957 by Olivier Laurent
pep8
4187
        upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
1789.2.4 by Christophe Chauvet
[FIX] ORM: Defult_field name in context belongs to curent object only, not to its relationals anymore
4188
1803.1.27 by Jay(Open ERP)
[FIX] default_xxx in context was passed to relational fields,corrected.
4189
        # default element in context must be removed when call a one2many or many2many
1794.1.1 by Jay(Open ERP)
[IMP] default_xx in context isuue
4190
        rel_context = context.copy()
1789.2.4 by Christophe Chauvet
[FIX] ORM: Defult_field name in context belongs to curent object only, not to its relationals anymore
4191
        for c in context.items():
4192
            if c[0].startswith('default_'):
4193
                del rel_context[c[0]]
4194
922 by Christophe Simonis
convert tabs to 4 spaces
4195
        for field in upd_todo:
4196
            for id in ids:
1844.2.2 by Fabien Pinckaers
[FIX] regression in fields.function computation
4197
                result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
922 by Christophe Simonis
convert tabs to 4 spaces
4198
3778.2.1 by Stefan Rijnhart
[FIX] Raise when an unknown field is being written to
4199
        unknown_fields = updend[:]
922 by Christophe Simonis
convert tabs to 4 spaces
4200
        for table in self._inherits:
4201
            col = self._inherits[table]
4202
            nids = []
2159.2.4 by Olivier Dony
[IMP] refactoring of check_access_rule to avoid SQL injection and simplify code
4203
            for sub_ids in cr.split_for_in_conditions(ids):
922 by Christophe Simonis
convert tabs to 4 spaces
4204
                cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
2334.1.8 by Anup(OpenERP)
[IMP] SQL injection Refactored
4205
                           'where id IN %s', (sub_ids,))
922 by Christophe Simonis
convert tabs to 4 spaces
4206
                nids.extend([x[0] for x in cr.fetchall()])
4207
4208
            v = {}
4209
            for val in updend:
957 by Olivier Laurent
pep8
4210
                if self._inherit_fields[val][0] == table:
4211
                    v[val] = vals[val]
3778.2.1 by Stefan Rijnhart
[FIX] Raise when an unknown field is being written to
4212
                    unknown_fields.remove(val)
2861 by Sébastien BEAU - http://www.akretion.com
[FIX]Speed improvement : Call of write() should go when there is vals
4213
            if v:
4214
                self.pool.get(table).write(cr, user, nids, v, context)
922 by Christophe Simonis
convert tabs to 4 spaces
4215
3778.2.1 by Stefan Rijnhart
[FIX] Raise when an unknown field is being written to
4216
        if unknown_fields:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
4217
            _logger.warning(
3778.2.4 by Stefan Rijnhart
[FIX] replace exception by warning, for friendly deprecation
4218
                'No such field(s) in model %s: %s.',
4219
                self._name, ', '.join(unknown_fields))
922 by Christophe Simonis
convert tabs to 4 spaces
4220
        self._validate(cr, user, ids, context)
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4221
4222
        # TODO: use _order to set dest at the right position and not first node of parent
4223
        # We can't defer parent_store computation because the stored function
4224
        # fields that are computer may refer (directly or indirectly) to
4225
        # parent_left/right (via a child_of domain)
4226
        if parents_changed:
974 by Fabien Pinckaers
Speed Improvement:
4227
            if self.pool._init:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4228
                self.pool._init_parent[self._name] = True
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
4229
            else:
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4230
                order = self._parent_order or self._order
4231
                parent_val = vals[self._parent_name]
4232
                if parent_val:
4233
                    clause, params = '%s=%%s' % (self._parent_name,), (parent_val,)
4234
                else:
4235
                    clause, params = '%s IS NULL' % (self._parent_name,), ()
4236
4237
                for id in parents_changed:
4238
                    cr.execute('SELECT parent_left, parent_right FROM %s WHERE id=%%s' % (self._table,), (id,))
4239
                    pleft, pright = cr.fetchone()
4240
                    distance = pright - pleft + 1
4241
3310.1.24 by Olivier Dony
[FIX] orm.write: avoid miscalculation of parent_left/right when moving multiple records under same parent
4242
                    # Positions of current siblings, to locate proper insertion point;
4243
                    # this can _not_ be fetched outside the loop, as it needs to be refreshed
4244
                    # after each update, in case several nodes are sequentially inserted one
4245
                    # next to the other (i.e computed incrementally)
3347 by Olivier Dony
[FIX] orm.write: parent_store updates must be processed in correct order to avoid edge cases
4246
                    cr.execute('SELECT parent_right, id FROM %s WHERE %s ORDER BY %s' % (self._table, clause, parent_order), params)
3310.1.24 by Olivier Dony
[FIX] orm.write: avoid miscalculation of parent_left/right when moving multiple records under same parent
4247
                    parents = cr.fetchall()
4248
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4249
                    # Find Position of the element
4250
                    position = None
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4251
                    for (parent_pright, parent_id) in parents:
4252
                        if parent_id == id:
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4253
                            break
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4254
                        position = parent_pright + 1
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4255
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4256
                    # It's the first node of the parent
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4257
                    if not position:
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4258
                        if not parent_val:
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4259
                            position = 1
1692.1.2 by Fabien Pinckaers
bugfix
4260
                        else:
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4261
                            cr.execute('select parent_left from '+self._table+' where id=%s', (parent_val,))
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4262
                            position = cr.fetchone()[0] + 1
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4263
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4264
                    if pleft < position <= pright:
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4265
                        raise except_orm(_('UserError'), _('Recursivity Detected.'))
4266
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4267
                    if pleft < position:
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4268
                        cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
4269
                        cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4270
                        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))
1729.1.13 by Fabien Pinckaers
bugfix_lp_331245
4271
                    else:
4272
                        cr.execute('update '+self._table+' set parent_left=parent_left+%s where parent_left>=%s', (distance, position))
4273
                        cr.execute('update '+self._table+' set parent_right=parent_right+%s where parent_right>=%s', (distance, position))
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4274
                        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))
922 by Christophe Simonis
convert tabs to 4 spaces
4275
1844.2.2 by Fabien Pinckaers
[FIX] regression in fields.function computation
4276
        result += self._store_get_values(cr, user, ids, vals.keys(), context)
2343 by Fabien Pinckaers
[FIX] store fields
4277
        result.sort()
4278
4279
        done = {}
3266 by Olivier Dony
[FIX] orm.write(): avoid name collision of "ids" variable, causing side-effects + some doc
4280
        for order, object, ids_to_update, fields_to_recompute in result:
4281
            key = (object, tuple(fields_to_recompute))
2343 by Fabien Pinckaers
[FIX] store fields
4282
            done.setdefault(key, {})
4283
            # avoid to do several times the same computation
4284
            todo = []
3266 by Olivier Dony
[FIX] orm.write(): avoid name collision of "ids" variable, causing side-effects + some doc
4285
            for id in ids_to_update:
2343 by Fabien Pinckaers
[FIX] store fields
4286
                if id not in done[key]:
4287
                    done[key][id] = True
4288
                    todo.append(id)
3266 by Olivier Dony
[FIX] orm.write(): avoid name collision of "ids" variable, causing side-effects + some doc
4289
            self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4290
4769.2.9 by Raphael Collet
[IMP] rename model method 'trigger_workflow' into 'step_workflow' (less confusing)
4291
        self.step_workflow(cr, user, ids, context=context)
922 by Christophe Simonis
convert tabs to 4 spaces
4292
        return True
4293
4294
    #
4295
    # TODO: Should set perm to user.xxx
4296
    #
4297
    def create(self, cr, user, vals, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4298
        """
3421 by Vo Minh Thu
[MERGE] res.user: fix the call to create_user:
4299
        Create a new record for the model.
4300
4301
        The values for the new record are initialized using the ``vals``
4302
        argument, and if necessary the result of ``default_get()``.
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4303
4304
        :param cr: database cursor
4305
        :param user: current user id
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4306
        :type user: integer
4307
        :param vals: field values for new record, e.g {'field_name': field_value, ...}
4308
        :type vals: dictionary
4309
        :param context: optional context arguments, e.g. {'lang': 'en_us', 'tz': 'UTC', ...}
4310
        :type context: dictionary
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4311
        :return: id of new record created
4312
        :raise AccessError: * if user has no create rights on the requested object
4313
                            * if user tries to bypass access rules for create on the requested object
4314
        :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4315
        :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)
4316
4317
        **Note**: The type of field values to pass in ``vals`` for relationship fields is specific.
4318
        Please see the description of the :py:meth:`~osv.osv.osv.write` method for details about the possible values and how
4319
        to specify them.
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4320
922 by Christophe Simonis
convert tabs to 4 spaces
4321
        """
4322
        if not context:
957 by Olivier Laurent
pep8
4323
            context = {}
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
4324
4325
        if self.is_transient():
4326
            self._transient_vacuum(cr, user)
4327
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
4328
        self.check_access_rights(cr, user, 'create')
922 by Christophe Simonis
convert tabs to 4 spaces
4329
4285.1.1 by Vo Minh Thu
[FIX] orm: disallow LOG_ACCESS fields in the create() method.
4330
        if self._log_access:
4331
            for f in LOG_ACCESS_COLUMNS:
4332
                if vals.pop(f, None) is not None:
4333
                    _logger.warning(
4334
                        'Field `%s` is not allowed when creating the model `%s`.',
4335
                        f, self._name)
2654 by Olivier Dony
[FIX,REF] orm: correct default values update during create() + refactor
4336
        vals = self._add_missing_default_values(cr, user, vals, context)
922 by Christophe Simonis
convert tabs to 4 spaces
4337
4338
        tocreate = {}
4339
        for v in self._inherits:
4340
            if self._inherits[v] not in vals:
4341
                tocreate[v] = {}
1845 by HDA(OpenERP)
[Merged]
4342
            else:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4343
                tocreate[v] = {'id': vals[self._inherits[v]]}
922 by Christophe Simonis
convert tabs to 4 spaces
4344
        (upd0, upd1, upd2) = ('', '', [])
4345
        upd_todo = []
3778.2.4 by Stefan Rijnhart
[FIX] replace exception by warning, for friendly deprecation
4346
        unknown_fields = []
922 by Christophe Simonis
convert tabs to 4 spaces
4347
        for v in vals.keys():
3552.1.1 by Thibaut DIRLIK
[FIX] create() does not write in the _inherits parent when fields have the same name.
4348
            if v in self._inherit_fields and v not in self._columns:
3549.4.3 by Naresh(OpenERP)
[FIX]:problem when coping the _inherits record the base class record was not created resulting the new record to refer the same parent that the old record is referring
4349
                (table, col, col_detail, original_parent) = self._inherit_fields[v]
922 by Christophe Simonis
convert tabs to 4 spaces
4350
                tocreate[table][v] = vals[v]
4351
                del vals[v]
1845 by HDA(OpenERP)
[Merged]
4352
            else:
4353
                if (v not in self._inherit_fields) and (v not in self._columns):
3778.2.4 by Stefan Rijnhart
[FIX] replace exception by warning, for friendly deprecation
4354
                    del vals[v]
4355
                    unknown_fields.append(v)
4356
        if unknown_fields:
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
4357
            _logger.warning(
3778.2.4 by Stefan Rijnhart
[FIX] replace exception by warning, for friendly deprecation
4358
                'No such field(s) in model %s: %s.',
4359
                self._name, ', '.join(unknown_fields))
1592 by Fabien Pinckaers
bugfixes
4360
1538 by Jay (Open ERP)
Bugfix : Record creation on databse views(ref : views used for dashboard).
4361
        # Try-except added to filter the creation of those records whose filds are readonly.
4362
        # Example : any dashboard which has all the fields readonly.(due to Views(database views))
4263.3.28 by Vo Minh Thu
[FIX]: orm: reverted a change in a previous commit (where a try/except was removed).
4363
        try:
4364
            cr.execute("SELECT nextval('"+self._sequence+"')")
4365
        except:
4366
            raise except_orm(_('UserError'),
4367
                _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.'))
1727 by mra (Open ERP)
fix : 329208 : trans_obj is not defined
4368
922 by Christophe Simonis
convert tabs to 4 spaces
4369
        id_new = cr.fetchone()[0]
4370
        for table in tocreate:
1845 by HDA(OpenERP)
[Merged]
4371
            if self._inherits[table] in vals:
4372
                del vals[self._inherits[table]]
2132 by Stephane Wirtel
[FIX] orm: Allows the inline modification of the _inherits fields
4373
4374
            record_id = tocreate[table].pop('id', None)
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
4375
4066.16.1 by Olivier Dony
[FIX] OPW 575395: orm: don't defer function field computation during creation of parent records (inheritS)
4376
            # When linking/creating parent records, force context without 'no_store_function' key that
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
4377
            # defers stored functions computing, as these won't be computed in batch at the end of create().
4066.16.1 by Olivier Dony
[FIX] OPW 575395: orm: don't defer function field computation during creation of parent records (inheritS)
4378
            parent_context = dict(context)
4379
            parent_context.pop('no_store_function', None)
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
4380
2225 by Jay(Open ERP)
[FIX] _inherits : Relational field if on the view of child object,should not raise error
4381
            if record_id is None or not record_id:
4066.16.1 by Olivier Dony
[FIX] OPW 575395: orm: don't defer function field computation during creation of parent records (inheritS)
4382
                record_id = self.pool.get(table).create(cr, user, tocreate[table], context=parent_context)
2132 by Stephane Wirtel
[FIX] orm: Allows the inline modification of the _inherits fields
4383
            else:
4066.16.1 by Olivier Dony
[FIX] OPW 575395: orm: don't defer function field computation during creation of parent records (inheritS)
4384
                self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=parent_context)
2132 by Stephane Wirtel
[FIX] orm: Allows the inline modification of the _inherits fields
4385
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4386
            upd0 += ',' + self._inherits[table]
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
4387
            upd1 += ',%s'
2132 by Stephane Wirtel
[FIX] orm: Allows the inline modification of the _inherits fields
4388
            upd2.append(record_id)
1845 by HDA(OpenERP)
[Merged]
4389
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4390
        #Start : Set bool fields to be False if they are not touched(to make search more powerful)
1775.1.3 by Jay(Open ERP)
[FIX] DB will save False Valuesfor booleans
4391
        bool_fields = [x for x in self._columns.keys() if self._columns[x]._type=='boolean']
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4392
1775.1.3 by Jay(Open ERP)
[FIX] DB will save False Valuesfor booleans
4393
        for bool_field in bool_fields:
4394
            if bool_field not in vals:
4395
                vals[bool_field] = False
4396
        #End
1845 by HDA(OpenERP)
[Merged]
4397
        for field in vals.copy():
4398
            fobj = None
4399
            if field in self._columns:
4400
                fobj = self._columns[field]
4401
            else:
4402
                fobj = self._inherit_fields[field][2]
4403
            if not fobj:
4404
                continue
4405
            groups = fobj.write
4406
            if groups:
4407
                edit = False
4408
                for group in groups:
4409
                    module = group.split(".")[0]
4410
                    grp = group.split(".")[1]
2334.1.8 by Anup(OpenERP)
[IMP] SQL injection Refactored
4411
                    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" % \
1845 by HDA(OpenERP)
[Merged]
4412
                               (grp, module, 'res.groups', user))
4413
                    readonly = cr.fetchall()
4414
                    if readonly[0][0] >= 1:
4415
                        edit = True
4416
                        break
4417
                    elif readonly[0][0] == 0:
4418
                        edit = False
4419
                    else:
4420
                        edit = False
4421
4422
                if not edit:
4423
                    vals.pop(field)
922 by Christophe Simonis
convert tabs to 4 spaces
4424
        for field in vals:
1845 by HDA(OpenERP)
[Merged]
4425
            if self._columns[field]._classic_write:
4426
                upd0 = upd0 + ',"' + field + '"'
4427
                upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
4428
                upd2.append(self._columns[field]._symbol_set[1](vals[field]))
4761.2.20 by Antonin Bourguignon
[IMP] fix a comment's indentation, remove a few whitespaces
4429
                #for the function fields that receive a value, we set them directly in the database
4492.1.1 by Quentin (OpenERP)
[FIX] osv/orm: compute the _fct_inv() of stored functional fields at the record creation. Compute also the _fct_inv() of related fields (because there is no reason not to do it). That last patch revert a 'fix' of lp bug 544087 introduced in revision 4182, i don't get the purpose of it but that bug is not repdocutible anymore...
4430
                #(they may be required), but we also need to trigger the _fct_inv()
4492.1.4 by Quentin (OpenERP)
[REV] osv/orm: revert of a previous patch that was unifying the behavior for fields.related and fields.function at creation time because it's a bit dangereous to treat it right now, and may need the add of a new field attribute. "I'll be back", said the patch
4431
                if (hasattr(self._columns[field], '_fnct_inv')) and not isinstance(self._columns[field], fields.related):
4432
                    #TODO: this way to special case the related fields is really creepy but it shouldn't be changed at
4433
                    #one week of the release candidate. It seems the only good way to handle correctly this is to add an
4434
                    #attribute to make a field `really readonly´ and thus totally ignored by the create()... otherwise
4435
                    #if, for example, the related has a default value (for usability) then the fct_inv is called and it
4436
                    #may raise some access rights error. Changing this is a too big change for now, and is thus postponed
4437
                    #after the release but, definitively, the behavior shouldn't be different for related and function
4438
                    #fields.
4492.1.1 by Quentin (OpenERP)
[FIX] osv/orm: compute the _fct_inv() of stored functional fields at the record creation. Compute also the _fct_inv() of related fields (because there is no reason not to do it). That last patch revert a 'fix' of lp bug 544087 introduced in revision 4182, i don't get the purpose of it but that bug is not repdocutible anymore...
4439
                    upd_todo.append(field)
1845 by HDA(OpenERP)
[Merged]
4440
            else:
4492.1.4 by Quentin (OpenERP)
[REV] osv/orm: revert of a previous patch that was unifying the behavior for fields.related and fields.function at creation time because it's a bit dangereous to treat it right now, and may need the add of a new field attribute. "I'll be back", said the patch
4441
                #TODO: this `if´ statement should be removed because there is no good reason to special case the fields
4442
                #related. See the above TODO comment for further explanations.
4443
                if not isinstance(self._columns[field], fields.related):
4444
                    upd_todo.append(field)
922 by Christophe Simonis
convert tabs to 4 spaces
4445
            if field in self._columns \
4446
                    and hasattr(self._columns[field], 'selection') \
4447
                    and vals[field]:
3129.1.3 by Olivier Dony
[FIX] fields.reference: don't accept half-defined references - second part of fix, in create()
4448
                self._check_selection_field_value(cr, user, field, vals[field], context=context)
922 by Christophe Simonis
convert tabs to 4 spaces
4449
        if self._log_access:
4104.2.1 by Samus Aran
[IMP] Creating new row in db also update write_date and write_uid
4450
            upd0 += ',create_uid,create_date,write_uid,write_date'
4451
            upd1 += ",%s,(now() at time zone 'UTC'),%s,(now() at time zone 'UTC')"
4452
            upd2.extend((user, user))
922 by Christophe Simonis
convert tabs to 4 spaces
4453
        cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
957 by Olivier Laurent
pep8
4454
        upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
922 by Christophe Simonis
convert tabs to 4 spaces
4455
2238.1.56 by Christophe Simonis
[IMP] orm: allow import and record creation to defer the computation of parent_left/right,[IMP] orm: do not update parent_left/right if the parent haven't been changed
4456
        if self._parent_store and not context.get('defer_parent_store_computation'):
974 by Fabien Pinckaers
Speed Improvement:
4457
            if self.pool._init:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4458
                self.pool._init_parent[self._name] = True
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
4459
            else:
974 by Fabien Pinckaers
Speed Improvement:
4460
                parent = vals.get(self._parent_name, False)
4461
                if parent:
1702.1.1 by Fabien Pinckaers
bugfix
4462
                    cr.execute('select parent_right from '+self._table+' where '+self._parent_name+'=%s order by '+(self._parent_order or self._order), (parent,))
1692.1.2 by Fabien Pinckaers
bugfix
4463
                    pleft_old = None
4464
                    result_p = cr.fetchall()
4465
                    for (pleft,) in result_p:
4466
                        if not pleft:
4467
                            break
4468
                        pleft_old = pleft
4469
                    if not pleft_old:
4470
                        cr.execute('select parent_left from '+self._table+' where id=%s', (parent,))
4471
                        pleft_old = cr.fetchone()[0]
4472
                    pleft = pleft_old
974 by Fabien Pinckaers
Speed Improvement:
4473
                else:
4474
                    cr.execute('select max(parent_right) from '+self._table)
4475
                    pleft = cr.fetchone()[0] or 0
1341.1.20 by Christophe Simonis
[FIX] psycopg2: %d -> %s
4476
                cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%s', (pleft,))
4477
                cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%s', (pleft,))
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4478
                cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4479
1845 by HDA(OpenERP)
[Merged]
4480
        # default element in context must be remove when call a one2many or many2many
1794.1.1 by Jay(Open ERP)
[IMP] default_xx in context isuue
4481
        rel_context = context.copy()
1789.2.4 by Christophe Chauvet
[FIX] ORM: Defult_field name in context belongs to curent object only, not to its relationals anymore
4482
        for c in context.items():
4483
            if c[0].startswith('default_'):
4484
                del rel_context[c[0]]
1845 by HDA(OpenERP)
[Merged]
4485
1849 by HDA(OpenERP)
rename html2html to Mako2html Merging improvement in orm
4486
        result = []
1765 by Fabien Pinckaers
bugfix
4487
        for field in upd_todo:
1849 by HDA(OpenERP)
rename html2html to Mako2html Merging improvement in orm
4488
            result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
1765 by Fabien Pinckaers
bugfix
4489
        self._validate(cr, user, [id_new], context)
961 by Fabien Pinckaers
Speed Improvement in recursive functions and trees:
4490
1849 by HDA(OpenERP)
rename html2html to Mako2html Merging improvement in orm
4491
        if not context.get('no_store_function', False):
4492
            result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
4493
            result.sort()
4494
            done = []
4495
            for order, object, ids, fields2 in result:
4496
                if not (object, ids, fields2) in done:
4497
                    self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
4498
                    done.append((object, ids, fields2))
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4499
2263 by Fabien Pinckaers
[IMP] logs background activities
4500
        if self._log_create and not (context and context.get('no_store_function', False)):
4501
            message = self._description + \
4502
                " '" + \
4503
                self.name_get(cr, user, [id_new], context=context)[0][1] + \
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4504
                "' " + _("created.")
2265 by Fabien Pinckaers
[FIX] Improve logging system
4505
            self.log(cr, user, id_new, message, True, context=context)
4743.17.1 by Chris Biersbach
[FIX] orm: moves the access_rule check to the end of the create method to avoid getting errors when trying to create and m2m values are passed, which can cause the rules to fail
4506
        self.check_access_rule(cr, user, [id_new], 'create', context=context)
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
4507
        self.create_workflow(cr, user, [id_new], context=context)
922 by Christophe Simonis
convert tabs to 4 spaces
4508
        return id_new
4509
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
4510
    def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
4511
        """Fetch records as objects allowing to use dot notation to browse fields and relations
4512
4513
        :param cr: database cursor
3980 by Xavier Morel
[IMP] move browse_record to logging, __init__ docstring to sphinx info fields
4514
        :param uid: current user id
3511.1.6 by Vo Minh Thu
[REF] orm: merged orm_template inside orm.
4515
        :param select: id or list of ids.
4516
        :param context: context arguments, like lang, time zone
4517
        :rtype: object or list of objects requested
4518
4519
        """
4520
        self._list_class = list_class or browse_record_list
4521
        cache = {}
4522
        # need to accepts ints and longs because ids coming from a method
4523
        # launched by button in the interface have a type long...
4524
        if isinstance(select, (int, long)):
4525
            return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
4526
        elif isinstance(select, list):
4527
            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)
4528
        else:
4529
            return browse_null()
4530
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4531
    def _store_get_values(self, cr, uid, ids, fields, context):
3266 by Olivier Dony
[FIX] orm.write(): avoid name collision of "ids" variable, causing side-effects + some doc
4532
        """Returns an ordered list of fields.functions to call due to
4533
           an update operation on ``fields`` of records with ``ids``,
4534
           obtained by calling the 'store' functions of these fields,
4535
           as setup by their 'store' attribute.
4536
4537
           :return: [(priority, model_name, [record_ids,], [function_fields,])]
4538
        """
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
4539
        if fields is None: fields = []
4540
        stored_functions = self.pool._store_function.get(self._name, [])
4541
4542
        # use indexed names for the details of the stored_functions:
4543
        model_name_, func_field_to_compute_, id_mapping_fnct_, trigger_fields_, priority_ = range(5)
4544
4545
        # only keep functions that should be triggered for the ``fields``
4066.26.2 by Xavier ALT
[FIX] OPW 577292: orm: force computing store values for all columns on unlink()
4546
        # being written to.
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
4547
        to_compute = [f for f in stored_functions \
4066.26.2 by Xavier ALT
[FIX] OPW 577292: orm: force computing store values for all columns on unlink()
4548
                if ((not f[trigger_fields_]) or set(fields).intersection(f[trigger_fields_]))]
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
4549
4550
        mapping = {}
4551
        for function in to_compute:
3480 by Olivier Dony
[FIX] orm.unlink(): cascade deletion of ir.model.data performed as admin
4552
            # use admin user for accessing objects having rules defined on store fields
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
4553
            target_ids = [id for id in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id]
3612 by Olivier Dony
[FIX] orm: refactoring and fix of stored functions processing by _store_get_values
4554
4555
            # the compound key must consider the priority and model name
4556
            key = (function[priority_], function[model_name_])
4557
            for target_id in target_ids:
4558
                mapping.setdefault(key, {}).setdefault(target_id,set()).add(tuple(function))
4559
4560
        # Here mapping looks like:
4561
        # { (10, 'model_a') : { target_id1: [ (function_1_tuple, function_2_tuple) ], ... }
4562
        #   (20, 'model_a') : { target_id2: [ (function_3_tuple, function_4_tuple) ], ... }
4563
        #   (99, 'model_a') : { target_id1: [ (function_5_tuple, function_6_tuple) ], ... }
4564
        # }
4565
4566
        # Now we need to generate the batch function calls list
4567
        # call_map =
4568
        #   { (10, 'model_a') : [(10, 'model_a', [record_ids,], [function_fields,])] }
4569
        call_map = {}
4570
        for ((priority,model), id_map) in mapping.iteritems():
4571
            functions_ids_maps = {}
4572
            # function_ids_maps =
4573
            #   { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] }
4574
            for id, functions in id_map.iteritems():
4575
                functions_ids_maps.setdefault(tuple(functions), []).append(id)
4576
            for functions, ids in functions_ids_maps.iteritems():
4577
                call_map.setdefault((priority,model),[]).append((priority, model, ids,
4578
                                                                 [f[func_field_to_compute_] for f in functions]))
4579
        ordered_keys = call_map.keys()
4580
        ordered_keys.sort()
4581
        result = []
4582
        if ordered_keys:
4583
            result = reduce(operator.add, (call_map[k] for k in ordered_keys))
4584
        return result
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4585
4586
    def _store_set_values(self, cr, uid, ids, fields, context):
3266 by Olivier Dony
[FIX] orm.write(): avoid name collision of "ids" variable, causing side-effects + some doc
4587
        """Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
4588
           respecting ``multi`` attributes), and stores the resulting values in the database directly."""
2343 by Fabien Pinckaers
[FIX] store fields
4589
        if not ids:
4590
            return True
1845 by HDA(OpenERP)
[Merged]
4591
        field_flag = False
4592
        field_dict = {}
4593
        if self._log_access:
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4594
            cr.execute('select id,write_date from '+self._table+' where id IN %s', (tuple(ids),))
1845 by HDA(OpenERP)
[Merged]
4595
            res = cr.fetchall()
4596
            for r in res:
4597
                if r[1]:
4598
                    field_dict.setdefault(r[0], [])
4599
                    res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
4600
                    write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
4601
                    for i in self.pool._store_function.get(self._name, []):
4602
                        if i[5]:
4603
                            up_write_date = write_date + datetime.timedelta(hours=i[5])
4604
                            if datetime.datetime.now() < up_write_date:
4605
                                if i[1] in fields:
4606
                                    field_dict[r[0]].append(i[1])
4607
                                    if not field_flag:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4608
                                        field_flag = True
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4609
        todo = {}
1398 by Fabien Pinckaers
improvements_bugfixes
4610
        keys = []
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4611
        for f in fields:
1398 by Fabien Pinckaers
improvements_bugfixes
4612
            if self._columns[f]._multi not in keys:
4613
                keys.append(self._columns[f]._multi)
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4614
            todo.setdefault(self._columns[f]._multi, [])
4615
            todo[self._columns[f]._multi].append(f)
1398 by Fabien Pinckaers
improvements_bugfixes
4616
        for key in keys:
4617
            val = todo[key]
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4618
            if key:
3480 by Olivier Dony
[FIX] orm.unlink(): cascade deletion of ir.model.data performed as admin
4619
                # use admin user for accessing objects having rules defined on store fields
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
4620
                result = self._columns[val[0]].get(cr, self, ids, val, SUPERUSER_ID, context=context)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4621
                for id, value in result.items():
1845 by HDA(OpenERP)
[Merged]
4622
                    if field_flag:
4623
                        for f in value.keys():
4624
                            if f in field_dict[id]:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4625
                                value.pop(f)
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4626
                    upd0 = []
4627
                    upd1 = []
4628
                    for v in value:
1398 by Fabien Pinckaers
improvements_bugfixes
4629
                        if v not in val:
4630
                            continue
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
4631
                        if self._columns[v]._type == 'many2one':
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4632
                            try:
4633
                                value[v] = value[v][0]
4634
                            except:
4635
                                pass
4636
                        upd0.append('"'+v+'"='+self._columns[v]._symbol_set[0])
4637
                        upd1.append(self._columns[v]._symbol_set[1](value[v]))
4638
                    upd1.append(id)
2169 by Harry (Open ERP)
[FIX] orm: fixed problem on function field with store=True in create method
4639
                    if upd0 and upd1:
4640
                        cr.execute('update "' + self._table + '" set ' + \
2682 by olt at tinyerp
[FIX] missing comma in sql query 'execute' call ("select count...")
4641
                            ','.join(upd0) + ' where id = %s', upd1)
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4642
4643
            else:
4644
                for f in val:
3480 by Olivier Dony
[FIX] orm.unlink(): cascade deletion of ir.model.data performed as admin
4645
                    # use admin user for accessing objects having rules defined on store fields
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
4646
                    result = self._columns[f].get(cr, self, ids, f, SUPERUSER_ID, context=context)
1845 by HDA(OpenERP)
[Merged]
4647
                    for r in result.keys():
4648
                        if field_flag:
4649
                            if r in field_dict.keys():
4650
                                if f in field_dict[r]:
1865.1.1 by mra (Open ERP)
[FIX] osv_memory object search method: now it will accept id as domain with its action
4651
                                    result.pop(r)
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4652
                    for id, value in result.items():
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
4653
                        if self._columns[f]._type == 'many2one':
1390 by Fabien Pinckaers
bugfix_cache_speed_improvement_store
4654
                            try:
4655
                                value = value[0]
4656
                            except:
4657
                                pass
4658
                        cr.execute('update "' + self._table + '" set ' + \
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
4659
                            '"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
922 by Christophe Simonis
convert tabs to 4 spaces
4660
        return True
4661
4662
    #
4663
    # TODO: Validate
4664
    #
4665
    def perm_write(self, cr, user, ids, fields, context=None):
2287 by olt at tinyerp
[FIX] orm: raise NotImplementedError instead of strings
4666
        raise NotImplementedError(_('This method does not exist anymore'))
922 by Christophe Simonis
convert tabs to 4 spaces
4667
4668
    # TODO: ameliorer avec NULL
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4669
    def _where_calc(self, cr, user, domain, active_test=True, context=None):
2593 by Olivier Dony
[FIX] orm: fix search() with sorting by inherited field
4670
        """Computes the WHERE clause needed to implement an OpenERP domain.
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4671
        :param domain: the domain to compute
4672
        :type domain: list
2593 by Olivier Dony
[FIX] orm: fix search() with sorting by inherited field
4673
        :param active_test: whether the default filtering of records with ``active``
2743 by nch at tinyerp
[FIX]:default sorting in group by
4674
                            field set to ``False`` should be applied.
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4675
        :return: the query expressing the given domain as provided in domain
4676
        :rtype: osv.query.Query
2593 by Olivier Dony
[FIX] orm: fix search() with sorting by inherited field
4677
        """
922 by Christophe Simonis
convert tabs to 4 spaces
4678
        if not context:
957 by Olivier Laurent
pep8
4679
            context = {}
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4680
        domain = domain[:]
922 by Christophe Simonis
convert tabs to 4 spaces
4681
        # if the object has a field named 'active', filter out all inactive
4682
        # records unless they were explicitely asked for
4066.1.10 by Olivier Dony
[FIX] orm: inherited `active` fields should be used for the implicit filtering
4683
        if 'active' in self._all_columns and (active_test and context.get('active_test', True)):
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4684
            if domain:
4066.1.10 by Olivier Dony
[FIX] orm: inherited `active` fields should be used for the implicit filtering
4685
                # the item[0] trick below works for domain items and '&'/'|'/'!'
4686
                # operators too
4687
                if not any(item[0] == 'active' for item in domain):
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4688
                    domain.insert(0, ('active', '=', 1))
966 by Christophe Simonis
modify the expression parser to use a (more) real polish notation.
4689
            else:
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4690
                domain = [('active', '=', 1)]
941 by Christophe Simonis
move domain parsing into expression class
4691
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4692
        if domain:
3515.4.2 by Vo Minh Thu
[REF] expression: instanciating an expression now "parses" it, moved some methods as functions, introduced some nice globals.
4693
            e = expression.expression(cr, user, domain, self, context)
941 by Christophe Simonis
move domain parsing into expression class
4694
            tables = e.get_tables()
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4695
            where_clause, where_params = e.to_sql()
4696
            where_clause = where_clause and [where_clause] or []
941 by Christophe Simonis
move domain parsing into expression class
4697
        else:
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4698
            where_clause, where_params, tables = [], [], ['"%s"' % self._table]
941 by Christophe Simonis
move domain parsing into expression class
4699
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4700
        return Query(tables, where_clause, where_params)
922 by Christophe Simonis
convert tabs to 4 spaces
4701
4702
    def _check_qorder(self, word):
4703
        if not regex_order.match(word):
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4704
            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)'))
922 by Christophe Simonis
convert tabs to 4 spaces
4705
        return True
4706
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4707
    def _apply_ir_rules(self, cr, uid, query, mode='read', context=None):
2783.2.10 by jas
[IMP] : base : improved Warnings
4708
        """Add what's missing in ``query`` to implement all appropriate ir.rules
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4709
          (using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
4710
4711
           :param query: the current query object
4712
        """
2826 by Olivier Dony
[FIX] orm.search: properly JOIN parent table when applying inherited ir.rules
4713
        def apply_rule(added_clause, added_params, added_tables, parent_model=None, child_object=None):
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4714
            """ :param string parent_model: string of the parent model
4715
                :param model child_object: model object, base of the rule application
4716
            """
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4717
            if added_clause:
2826 by Olivier Dony
[FIX] orm.search: properly JOIN parent table when applying inherited ir.rules
4718
                if parent_model and child_object:
4719
                    # as inherited rules are being applied, we need to add the missing JOIN
4720
                    # to reach the parent table (if it was not JOINed yet in the query)
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4721
                    parent_alias = child_object._inherits_join_add(child_object, parent_model, query)
4722
                    # inherited rules are applied on the external table -> need to get the alias and replace
4723
                    parent_table = self.pool.get(parent_model)._table
4724
                    added_clause = [clause.replace('"%s"' % parent_table, '"%s"' % parent_alias) for clause in added_clause]
4601.2.48 by Thibault Delavallée
[CLEAN] orm: added quote around a forgottent table name; cleaned a bit some code and added comments, removed dead code.
4725
                    # change references to parent_table to parent_alias, because we now use the alias to refer to the table
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4726
                    new_tables = []
4727
                    for table in added_tables:
4601.2.48 by Thibault Delavallée
[CLEAN] orm: added quote around a forgottent table name; cleaned a bit some code and added comments, removed dead code.
4728
                        # table is just a table name -> switch to the full alias
4682.2.9 by Xavier Morel
[REM] unnecessary parens
4729
                        if table == '"%s"' % parent_table:
4601.2.48 by Thibault Delavallée
[CLEAN] orm: added quote around a forgottent table name; cleaned a bit some code and added comments, removed dead code.
4730
                            new_tables.append('"%s" as "%s"' % (parent_table, parent_alias))
4731
                        # table is already a full statement -> replace reference to the table to its alias, is correct with the way aliases are generated
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4732
                        else:
4601.2.48 by Thibault Delavallée
[CLEAN] orm: added quote around a forgottent table name; cleaned a bit some code and added comments, removed dead code.
4733
                            new_tables.append(table.replace('"%s"' % parent_table, '"%s"' % parent_alias))
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4734
                    added_tables = new_tables
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4735
                query.where_clause += added_clause
4736
                query.where_clause_params += added_params
4737
                for table in added_tables:
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4738
                    if table not in query.tables:
4739
                        query.tables.append(table)
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4740
                return True
4741
            return False
2793.1.3 by Olivier Dony
[IMP] osv.query,orm: removed trailing whitespace introduced by previous commits
4742
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4743
        # apply main rules on the object
4744
        rule_obj = self.pool.get('ir.rule')
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4745
        rule_where_clause, rule_where_clause_params, rule_tables = rule_obj.domain_get(cr, uid, self._name, mode, context=context)
4746
        apply_rule(rule_where_clause, rule_where_clause_params, rule_tables)
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4747
2826 by Olivier Dony
[FIX] orm.search: properly JOIN parent table when applying inherited ir.rules
4748
        # apply ir.rules from the parents (through _inherits)
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4749
        for inherited_model in self._inherits:
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4750
            rule_where_clause, rule_where_clause_params, rule_tables = rule_obj.domain_get(cr, uid, inherited_model, mode, context=context)
4751
            apply_rule(rule_where_clause, rule_where_clause_params, rule_tables,
4752
                        parent_model=inherited_model, child_object=self)
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4753
4754
    def _generate_m2o_order_by(self, order_field, query):
4755
        """
4756
        Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4757
        either native m2o fields or function/related fields that are stored, including
4758
        intermediate JOINs for inheritance if required.
2793.1.3 by Olivier Dony
[IMP] osv.query,orm: removed trailing whitespace introduced by previous commits
4759
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4760
        :return: the qualified field name to use in an ORDER BY clause to sort by ``order_field``
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4761
        """
4762
        if order_field not in self._columns and order_field in self._inherit_fields:
4763
            # also add missing joins for reaching the table containing the m2o field
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4764
            qualified_field = self._inherits_join_calc(order_field, query)
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4765
            order_field_column = self._inherit_fields[order_field][2]
4766
        else:
4767
            qualified_field = '"%s"."%s"' % (self._table, order_field)
4768
            order_field_column = self._columns[order_field]
4769
4770
        assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4771
        if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
3976.1.7 by Vo Minh Thu
[IMP] openerp.{modules,osv}: _logger with fully qualified module name.
4772
            _logger.debug("Many2one function/related fields must be stored " \
4773
                "to be used as ordering fields! Ignoring sorting for %s.%s",
4774
                self._name, order_field)
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4775
            return
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4776
4777
        # figure out the applicable order_by for the m2o
4778
        dest_model = self.pool.get(order_field_column._obj)
4779
        m2o_order = dest_model._order
4780
        if not regex_order.match(m2o_order):
4781
            # _order is complex, can't use it here, so we default to _rec_name
4782
            m2o_order = dest_model._rec_name
4783
        else:
3143.1.2 by Olivier Dony
[IMP] orm.search: support multiple _order clauses in destination objects when sorting on m2o fields
4784
            # extract the field names, to be able to qualify them and add desc/asc
4785
            m2o_order_list = []
3292 by Anup(OpenERP)
[FIX] orm.search: fix ordering when multiple order columns are used
4786
            for order_part in m2o_order.split(","):
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4787
                m2o_order_list.append(order_part.strip().split(" ", 1)[0].strip())
3143.1.2 by Olivier Dony
[IMP] orm.search: support multiple _order clauses in destination objects when sorting on m2o fields
4788
            m2o_order = m2o_order_list
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4789
2783.2.10 by jas
[IMP] : base : improved Warnings
4790
        # Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4791
        # as we don't want to exclude results that have NULL values for the m2o
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4792
        src_table, src_field = qualified_field.replace('"', '').split('.', 1)
4601.2.44 by Thibault Delavallée
[CLEAN] Query: cleaned a bit the code. All joins now goes through the same method, either implicit or explicit. Will have to be upgraded in future versions, but at least this is a bit centralized. Updated ORM accordingly. Updated tests. Added a get_alias_from_query method in expression that find the table and the alias from a 'full alias' statement.
4793
        dst_alias, dst_alias_statement = query.add_join((src_table, dest_model._table, src_field, 'id', src_field), implicit=False, outer=True)
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4794
        qualify = lambda field: '"%s"."%s"' % (dst_alias, field)
3143.1.2 by Olivier Dony
[IMP] orm.search: support multiple _order clauses in destination objects when sorting on m2o fields
4795
        return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4796
4797
    def _generate_order_by(self, order_spec, query):
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4798
        """
4799
        Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
4800
        a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
4801
4802
        :raise" except_orm in case order_spec is malformed
4803
        """
4601.2.45 by Thibault Delavallée
[MERGE] Merged branch holding a proposal to solve the generate_order_by issue (see lp:1070757). As requested by Olivier Donny, this fix has been moved into generate_order_by. Deleted previous workaround done in this branch. Patch courtesy of Paulius Sladkevičius
4804
        order_by_clause = ''
4717.1.3 by Thibault Delavallée
[REV] Reverted last improvement, because this was messing with the menus.
4805
        order_spec = order_spec or self._order
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4806
        if order_spec:
4807
            order_by_elements = []
4808
            self._check_qorder(order_spec)
4809
            for order_part in order_spec.split(','):
4810
                order_split = order_part.strip().split(' ')
4811
                order_field = order_split[0].strip()
4812
                order_direction = order_split[1].strip() if len(order_split) == 2 else ''
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4813
                inner_clause = None
2830.3.1 by Julien Thewys
[FIX] generate_order_by now supports 'id' order_field.
4814
                if order_field == 'id':
4579.1.4 by Quentin (OpenERP)
[FIX] osv/orm: fixed the use of 'order' argument given to a search function in order to allow to order by 'ID DESC' and by '<another valid order> + ID ASC/DESC'
4815
                    order_by_elements.append('"%s"."id" %s' % (self._table, order_direction))
2830.3.1 by Julien Thewys
[FIX] generate_order_by now supports 'id' order_field.
4816
                elif order_field in self._columns:
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4817
                    order_column = self._columns[order_field]
4818
                    if order_column._classic_read:
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4819
                        inner_clause = '"%s"."%s"' % (self._table, order_field)
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4820
                    elif order_column._type == 'many2one':
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4821
                        inner_clause = self._generate_m2o_order_by(order_field, query)
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4822
                    else:
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4823
                        continue  # ignore non-readable or "non-joinable" fields
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4824
                elif order_field in self._inherit_fields:
3549.4.1 by Naresh(OpenERP)
[FIX]:_inherits for multilevel inheritence
4825
                    parent_obj = self.pool.get(self._inherit_fields[order_field][3])
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4826
                    order_column = parent_obj._columns[order_field]
4827
                    if order_column._classic_read:
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4828
                        inner_clause = self._inherits_join_calc(order_field, query)
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4829
                    elif order_column._type == 'many2one':
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4830
                        inner_clause = self._generate_m2o_order_by(order_field, query)
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4831
                    else:
4601.2.40 by Thibault Delavallée
[IMP] [WIP] order_by: now using aliases in _generate_order_by, _generate_o2m_order_by, _inherits_join_add, ... added some tests. Next comits will clean a bit the code, because currently it is a bit messy.
4832
                        continue  # ignore non-readable or "non-joinable" fields
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4833
                if inner_clause:
3143.1.2 by Olivier Dony
[IMP] orm.search: support multiple _order clauses in destination objects when sorting on m2o fields
4834
                    if isinstance(inner_clause, list):
4835
                        for clause in inner_clause:
4836
                            order_by_elements.append("%s %s" % (clause, order_direction))
4837
                    else:
4838
                        order_by_elements.append("%s %s" % (inner_clause, order_direction))
3094 by Olivier Dony
[IMP] orm.search: more quietly ignore invalid m2o order spec + fallback to default _order
4839
            if order_by_elements:
4840
                order_by_clause = ",".join(order_by_elements)
2728.1.12 by Olivier Dony
[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields
4841
4842
        return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
4843
2675 by Olivier Dony
[FIX] orm: fields_view_get selection values must properly heed ir.rules:
4844
    def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4845
        """
2743 by nch at tinyerp
[FIX]:default sorting in group by
4846
        Private implementation of search() method, allowing specifying the uid to use for the access right check.
2675 by Olivier Dony
[FIX] orm: fields_view_get selection values must properly heed ir.rules:
4847
        This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
4848
        by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
4849
        This is ok at the security level because this method is private and not callable through XML-RPC.
2743 by nch at tinyerp
[FIX]:default sorting in group by
4850
2675 by Olivier Dony
[FIX] orm: fields_view_get selection values must properly heed ir.rules:
4851
        :param access_rights_uid: optional user ID to use when checking access rights
4852
                                  (not for ir.rules, this is only for ir.model.access)
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4853
        """
2238.1.25 by Albert Cervera i Areny - http://www.NaN-tic.com
[FIX] Exploit Broken : Search should have checked access rights of read
4854
        if context is None:
922 by Christophe Simonis
convert tabs to 4 spaces
4855
            context = {}
4344.1.16 by Fabien Pinckaers
[IMP] Removing a bad practice:
4856
        self.check_access_rights(cr, access_rights_uid or user, 'read')
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4857
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
4858
        # For transient models, restrict acces to the current user, except for the super-user
3511.1.35 by Olivier Dony
[IMP] start unifying the SUPERUSER_ID constant
4859
        if self.is_transient() and self._log_access and user != SUPERUSER_ID:
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
4860
            args = expression.AND(([('create_uid', '=', user)], args or []))
4861
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4862
        query = self._where_calc(cr, user, args, context=context)
4863
        self._apply_ir_rules(cr, user, query, 'read', context=context)
4864
        order_by = self._generate_order_by(order, query)
4865
        from_clause, where_clause, where_clause_params = query.get_sql()
4866
922 by Christophe Simonis
convert tabs to 4 spaces
4867
        limit_str = limit and ' limit %d' % limit or ''
4868
        offset_str = offset and ' offset %d' % offset or ''
2793.1.1 by Olivier Dony
[ADD] osv: initial basic implementation of Query object:
4869
        where_str = where_clause and (" WHERE %s" % where_clause) or ''
4795.1.1 by Raphael Collet
[FIX] search: when count=True, execute main query as a subquery to avoid side effects with offset and limit
4870
        query_str = 'SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str
922 by Christophe Simonis
convert tabs to 4 spaces
4871
4872
        if count:
4795.1.1 by Raphael Collet
[FIX] search: when count=True, execute main query as a subquery to avoid side effects with offset and limit
4873
            # /!\ the main query must be executed as a subquery, otherwise
4874
            # offset and limit apply to the result of count()!
4802 by Olivier Dony
[MERGE] search: when count=True, execute main query as a subquery to avoid side effects with offset and limit
4875
            cr.execute('SELECT count(*) FROM (%s) AS count' % query_str, where_clause_params)
4795.1.1 by Raphael Collet
[FIX] search: when count=True, execute main query as a subquery to avoid side effects with offset and limit
4876
            res = cr.fetchone()
4877
            return res[0]
4878
4879
        cr.execute(query_str, where_clause_params)
922 by Christophe Simonis
convert tabs to 4 spaces
4880
        res = cr.fetchall()
4717.1.1 by Thibault Delavallée
[FIX] _search not uniquifies the results, because when using _auto_join we could have duplicates in the 'left part' of the result, i.e. a lead with several unread messages. This is done using a custom method instead of a set, because sets are unordered.
4881
4882
        # TDE note: with auto_join, we could have several lines about the same result
4883
        # i.e. a lead with several unread messages; we uniquify the result using
4884
        # a fast way to do it while preserving order (http://www.peterbe.com/plog/uniqifiers-benchmark)
4885
        def _uniquify_list(seq):
4886
            seen = set()
4887
            return [x for x in seq if x not in seen and not seen.add(x)]
4888
4889
        return _uniquify_list([x[0] for x in res])
922 by Christophe Simonis
convert tabs to 4 spaces
4890
4891
    # returns the different values ever entered for one field
4892
    # this is used, for example, in the client when the user hits enter on
4893
    # a char field
4894
    def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
4895
        if not args:
957 by Olivier Laurent
pep8
4896
            args = []
922 by Christophe Simonis
convert tabs to 4 spaces
4897
        if field in self._inherit_fields:
957 by Olivier Laurent
pep8
4898
            return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
922 by Christophe Simonis
convert tabs to 4 spaces
4899
        else:
4900
            return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
4901
1691 by Fabien Pinckaers
bugfix
4902
    def copy_data(self, cr, uid, id, default=None, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4903
        """
4904
        Copy given record's data with all its fields values
4905
4906
        :param cr: database cursor
4682.2.15 by Xavier Morel
[FIX] incorrect docstrings or docstring param names not matching actual param names
4907
        :param uid: current user id
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4908
        :param id: id of the record to copy
4909
        :param default: field values to override in the original values of the copied record
4910
        :type default: dictionary
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4911
        :param context: context arguments, like lang, time zone
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4912
        :type context: dictionary
2111.3.21 by Rvo(Open ERP)
[IMP]:improved orm's public methods docstrings
4913
        :return: dictionary containing all the field values
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
4914
        """
4915
2238.1.42 by Jay(Open ERP)
[FIX] fixed a bug in copy_data: it now copies the original (not translated) field value as the source value
4916
        if context is None:
957 by Olivier Laurent
pep8
4917
            context = {}
3055 by Olivier Dony
[MERGE+FIX] fixes for translations,sequence,copy,error messages - some patches courtesy of Margarita Manterola and Don Kirkby
4918
4919
        # avoid recursion through already copied records in case of circular relationship
4920
        seen_map = context.setdefault('__copy_data_seen',{})
4921
        if id in seen_map.setdefault(self._name,[]):
4922
            return
4923
        seen_map[self._name].append(id)
4924
2238.1.42 by Jay(Open ERP)
[FIX] fixed a bug in copy_data: it now copies the original (not translated) field value as the source value
4925
        if default is None:
922 by Christophe Simonis
convert tabs to 4 spaces
4926
            default = {}
4927
        if 'state' not in default:
4928
            if 'state' in self._defaults:
1897 by Christophe Simonis
[IMP] _defaults: allow to pass directly the value instead of a lambda
4929
                if callable(self._defaults['state']):
4930
                    default['state'] = self._defaults['state'](self, cr, uid, context)
4931
                else:
4932
                    default['state'] = self._defaults['state']
4933
3017.1.2 by P. Christeas
ORM: fix bug at orm.copy_data(), would reset context[lang]
4934
        context_wo_lang = context.copy()
2238.1.42 by Jay(Open ERP)
[FIX] fixed a bug in copy_data: it now copies the original (not translated) field value as the source value
4935
        if 'lang' in context:
4936
            del context_wo_lang['lang']
3017.1.3 by P. Christeas
ORM: Better message when copying non-existing records
4937
        data = self.read(cr, uid, [id,], context=context_wo_lang)
4938
        if data:
4939
            data = data[0]
4940
        else:
4941
            raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
4942
4457.2.1 by Raphael Collet
[FIX] orm: fix the method copy() when an 'inherits' field is given in parameter default
4943
        # build a black list of fields that should not be copied
4944
        blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right'])
4945
        def blacklist_given_fields(obj):
4946
            # blacklist the fields that are given by inheritance
4947
            for other, field_to_other in obj._inherits.items():
4948
                blacklist.add(field_to_other)
4949
                if field_to_other in default:
4950
                    # all the fields of 'other' are given by the record: default[field_to_other],
4951
                    # except the ones redefined in self
4952
                    blacklist.update(set(self.pool.get(other)._all_columns) - set(self._columns))
4953
                else:
4954
                    blacklist_given_fields(self.pool.get(other))
4955
        blacklist_given_fields(self)
4956
4957
        res = dict(default)
4958
        for f, colinfo in self._all_columns.items():
4959
            field = colinfo.column
922 by Christophe Simonis
convert tabs to 4 spaces
4960
            if f in default:
4457.2.1 by Raphael Collet
[FIX] orm: fix the method copy() when an 'inherits' field is given in parameter default
4961
                pass
4962
            elif f in blacklist:
4963
                pass
4964
            elif isinstance(field, fields.function):
4965
                pass
4966
            elif field._type == 'many2one':
4967
                res[f] = data[f] and data[f][0]
4968
            elif field._type == 'one2many':
4969
                other = self.pool.get(field._obj)
4970
                # duplicate following the order of the ids because we'll rely on
4971
                # it later for copying translations in copy_translation()!
4972
                lines = [other.copy_data(cr, uid, line_id, context=context) for line_id in sorted(data[f])]
4973
                # the lines are duplicated using the wrong (old) parent, but then
4974
                # are reassigned to the correct one thanks to the (0, 0, ...)
4975
                res[f] = [(0, 0, line) for line in lines if line]
4976
            elif field._type == 'many2many':
4977
                res[f] = [(6, 0, data[f])]
4978
            else:
4979
                res[f] = data[f]
4980
4981
        return res
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
4982
4983
    def copy_translations(self, cr, uid, old_id, new_id, context=None):
3055 by Olivier Dony
[MERGE+FIX] fixes for translations,sequence,copy,error messages - some patches courtesy of Margarita Manterola and Don Kirkby
4984
        if context is None:
4985
            context = {}
4986
4987
        # avoid recursion through already copied records in case of circular relationship
4988
        seen_map = context.setdefault('__copy_translations_seen',{})
4989
        if old_id in seen_map.setdefault(self._name,[]):
4990
            return
4991
        seen_map[self._name].append(old_id)
4992
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
4993
        trans_obj = self.pool.get('ir.translation')
3466.1.9 by Vo Minh Thu
[REF] orm: added TODOs.
4994
        # TODO it seems fields_get can be replaced by _all_columns (no need for translation)
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
4995
        fields = self.fields_get(cr, uid, context=context)
4996
4997
        translation_records = []
4998
        for field_name, field_def in fields.items():
4999
            # we must recursively copy the translations for o2o and o2m
4102.1.3 by Vo Minh Thu
[IMP+FIX] fields: removed references to one2one, but also
5000
            if field_def['type'] == 'one2many':
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5001
                target_obj = self.pool.get(field_def['relation'])
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
5002
                old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5003
                # here we rely on the order of the ids to match the translations
5004
                # as foreseen in copy_data()
2148.1.4 by Numerigraphe - Lionel Sausin
[IMP] wording: children intead of childs (again)
5005
                old_children = sorted(old_record[field_name])
5006
                new_children = sorted(new_record[field_name])
5007
                for (old_child, new_child) in zip(old_children, new_children):
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5008
                    target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
5009
            # and for translatable fields we keep them for copy
5010
            elif field_def.get('translate'):
5011
                trans_name = ''
5012
                if field_name in self._columns:
5013
                    trans_name = self._name + "," + field_name
5014
                elif field_name in self._inherit_fields:
5015
                    trans_name = self._inherit_fields[field_name][0] + "," + field_name
5016
                if trans_name:
5017
                    trans_ids = trans_obj.search(cr, uid, [
5018
                            ('name', '=', trans_name),
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
5019
                            ('res_id', '=', old_id)
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5020
                    ])
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
5021
                    translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5022
5023
        for record in translation_records:
5024
            del record['id']
5025
            record['res_id'] = new_id
5026
            trans_obj.create(cr, uid, record, context=context)
5027
1170 by Jay Vora
Corrected copy functionality for fields translated=True (Ref: Yajushi Yagnik)
5028
1691 by Fabien Pinckaers
bugfix
5029
    def copy(self, cr, uid, id, default=None, context=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
5030
        """
5031
        Duplicate record with given id updating it with default values
5032
5033
        :param cr: database cursor
5034
        :param uid: current user id
5035
        :param id: id of the record to copy
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
5036
        :param default: dictionary of field values to override in the original values of the copied record, e.g: ``{'field_name': overriden_value, ...}``
5037
        :type default: dictionary
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
5038
        :param context: context arguments, like lang, time zone
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
5039
        :type context: dictionary
3755 by Vo Minh Thu
[FIX] orm.copy(): corrected return value in docstring.
5040
        :return: id of the newly created record
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
5041
5042
        """
3055 by Olivier Dony
[MERGE+FIX] fixes for translations,sequence,copy,error messages - some patches courtesy of Margarita Manterola and Don Kirkby
5043
        if context is None:
5044
            context = {}
5045
        context = context.copy()
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5046
        data = self.copy_data(cr, uid, id, default, context)
1803.4.1 by Jay(Open ERP)
[FIX] Context passed on copy()
5047
        new_id = self.create(cr, uid, data, context)
2402 by Olivier Dony, Jay Vora
[FIX] orm: proper recursive copy of translations through one2many relationships during copy()
5048
        self.copy_translations(cr, uid, id, new_id, context)
1170 by Jay Vora
Corrected copy functionality for fields translated=True (Ref: Yajushi Yagnik)
5049
        return new_id
922 by Christophe Simonis
convert tabs to 4 spaces
5050
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
5051
    def exists(self, cr, uid, ids, context=None):
3511.1.22 by Olivier Dony
[MERGE] sync with latest trunk
5052
        """Checks whether the given id or ids exist in this model,
5053
           and return the list of ids that do. This is simple to use for
5054
           a truth test on a browse_record::
5055
5056
               if record.exists():
5057
                   pass
5058
5059
           :param ids: id or list of ids to check for existence
5060
           :type ids: int or [int]
5061
           :return: the list of ids that currently exist, out of
5062
                    the given `ids`
5063
        """
2685 by olt at tinyerp
[IMP] orm: added 'schema' and 'data' logger
5064
        if type(ids) in (int, long):
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
5065
            ids = [ids]
4682.2.9 by Xavier Morel
[REM] unnecessary parens
5066
        query = 'SELECT id FROM "%s"' % self._table
2235 by Quality Team
[FIX] Rewrite the field.property engine to work as expected (value per
5067
        cr.execute(query + "WHERE ID IN %s", (tuple(ids),))
3353.1.39 by Olivier Dony
[IMP] orm,orm_memory: more consistent implementations of exists() and check_access_rule()
5068
        return [x[0] for x in cr.fetchall()]
1786.1.1 by Christophe Simonis
[FIX] browse_record: raise a better exception when the id doesn't exists
5069
3084 by Olivier Dony
[IMP] orm,base: added context to _constraints functions + use generic osv.osv.check_recursion()
5070
    def check_recursion(self, cr, uid, ids, context=None, parent=None):
3976.1.18 by Vo Minh Thu
[IMP] warnings: turn warnings.warn into logging.warning:
5071
        _logger.warning("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \
5072
                        self._name)
3017.1.13 by Olivier Dony
[IMP] orm: proper test for column names in check_recursion()
5073
        assert parent is None or parent in self._columns or parent in self._inherit_fields,\
5074
                    "The 'parent' parameter passed to check_recursion() must be None or a valid field name"
3084 by Olivier Dony
[IMP] orm,base: added context to _constraints functions + use generic osv.osv.check_recursion()
5075
        return self._check_recursion(cr, uid, ids, context, parent)
3017.1.4 by P. Christeas
ORM: deprecate check_recursion() in favour of _check_recursion()
5076
3084 by Olivier Dony
[IMP] orm,base: added context to _constraints functions + use generic osv.osv.check_recursion()
5077
    def _check_recursion(self, cr, uid, ids, context=None, parent=None):
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
5078
        """
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
5079
        Verifies that there is no loop in a hierarchical structure of records,
5080
        by following the parent relationship using the **parent** field until a loop
5081
        is detected or until a top-level record is found.
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
5082
5083
        :param cr: database cursor
5084
        :param uid: current user id
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
5085
        :param ids: list of ids of records to check
5086
        :param parent: optional parent field name (default: ``self._parent_name = parent_id``)
5087
        :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
2111.3.15 by RVO(OpenERP)
Added docstring in public methods of orm
5088
        """
5089
922 by Christophe Simonis
convert tabs to 4 spaces
5090
        if not parent:
5091
            parent = self._parent_name
5092
        ids_parent = ids[:]
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
5093
        query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
5094
        while ids_parent:
922 by Christophe Simonis
convert tabs to 4 spaces
5095
            ids_parent2 = []
943 by Christophe Simonis
move, rename and refactor use of ID_MAX
5096
            for i in range(0, len(ids), cr.IN_MAX):
5097
                sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
2334.1.4 by Anup(OpenERP)
[IMP] removed the possible SQL injection server.
5098
                cr.execute(query, (tuple(sub_ids_parent),))
922 by Christophe Simonis
convert tabs to 4 spaces
5099
                ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
5100
            ids_parent = ids_parent2
5101
            for i in ids_parent:
5102
                if i in ids:
5103
                    return False
5104
        return True
1844.6.2 by HDA(OpenERP)
Merge and bugfixes from jvo
5105
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5106
    def _get_external_ids(self, cr, uid, ids, *args, **kwargs):
5107
        """Retrieve the External ID(s) of any database record.
3212 by Olivier Dony
[IMP] orm: added get_xml_ids() variant to support retrieving multiple IDs
5108
5109
        **Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
5110
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5111
        :return: map of ids to the list of their fully qualified External IDs
5112
                 in the form ``module.key``, or an empty list when there's no External
5113
                 ID for a record, e.g.::
5114
5115
                     { 'id': ['module.ext_id', 'module.ext_id_bis'],
5116
                       'id2': [] }
3212 by Olivier Dony
[IMP] orm: added get_xml_ids() variant to support retrieving multiple IDs
5117
        """
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5118
        ir_model_data = self.pool.get('ir.model.data')
5119
        data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
5120
        data_results = ir_model_data.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
3212 by Olivier Dony
[IMP] orm: added get_xml_ids() variant to support retrieving multiple IDs
5121
        result = {}
5122
        for id in ids:
5123
            # can't use dict.fromkeys() as the list would be shared!
5124
            result[id] = []
5125
        for record in data_results:
5126
            result[record['res_id']].append('%(module)s.%(name)s' % record)
5127
        return result
5128
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5129
    def get_external_id(self, cr, uid, ids, *args, **kwargs):
5130
        """Retrieve the External ID of any database record, if there
2284 by Olivier Dony
[ADD] osv.osv, base: added generic xml_id getter + added xml_id to views on ir.ui.views + minor fine-tuning of admin views
5131
        is one. This method works as a possible implementation
5132
        for a function field, to be able to add it to any
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5133
        model object easily, referencing it as ``Model.get_external_id``.
2284 by Olivier Dony
[ADD] osv.osv, base: added generic xml_id getter + added xml_id to views on ir.ui.views + minor fine-tuning of admin views
5134
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5135
        When multiple External IDs exist for a record, only one
3212 by Olivier Dony
[IMP] orm: added get_xml_ids() variant to support retrieving multiple IDs
5136
        of them is returned (randomly).
5137
5138
        :return: map of ids to their fully qualified XML ID,
2572 by Olivier Dony
[IMP] orm: docstring improvements: fixed incorrect RST indentation + improved doc a bit + some cleanup
5139
                 defaulting to an empty string when there's none
4466.1.2 by Christophe Simonis
[IMP] orm: remove trailing spaces
5140
                 (to be usable as a function field),
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5141
                 e.g.::
5142
5143
                     { 'id': 'module.ext_id',
5144
                       'id2': '' }
2284 by Olivier Dony
[ADD] osv.osv, base: added generic xml_id getter + added xml_id to views on ir.ui.views + minor fine-tuning of admin views
5145
        """
3297 by J-E Baudoux
[MERGE] orm,base: added ir.model introspection OOo report, made orm._get_xml_ids() private (return type incompatible with XML-RPC)
5146
        results = self._get_xml_ids(cr, uid, ids)
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5147
        for k, v in results.iteritems():
3212 by Olivier Dony
[IMP] orm: added get_xml_ids() variant to support retrieving multiple IDs
5148
            if results[k]:
5149
                results[k] = v[0]
5150
            else:
5151
                results[k] = ''
5152
        return results
2284 by Olivier Dony
[ADD] osv.osv, base: added generic xml_id getter + added xml_id to views on ir.ui.views + minor fine-tuning of admin views
5153
3427.2.34 by Olivier Dony
[IMP] orm: rename/cleanup: get_xml_id* -> get_external_id*
5154
    # backwards compatibility
5155
    get_xml_id = get_external_id
5156
    _get_xml_ids = _get_external_ids
4333.1.1 by Fabien Pinckaers
[IMP] cleaning of need action
5157
4802.3.19 by Vo Minh Thu
[IMP] orm: added a print_report() method.
5158
    def print_report(self, cr, uid, ids, name, data, context=None):
5159
        """
5160
        Render the report `name` for the given IDs. The report must be defined
5161
        for this model, not another.
5162
        """
5163
        report = self.pool['ir.actions.report.xml']._lookup_report(cr, name)
5164
        assert self._name == report.table
5165
        return report.create(cr, uid, ids, data, context)
5166
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5167
    # Transience
3511.1.11 by Vo Minh Thu
[REF] osv: replace isinstance(m,osv_memory) by m.is_transient().
5168
    def is_transient(self):
5169
        """ Return whether the model is transient.
5170
4408.2.21 by Xavier Morel
[DOC] fixes and cleanups
5171
        See :class:`TransientModel`.
3511.1.11 by Vo Minh Thu
[REF] osv: replace isinstance(m,osv_memory) by m.is_transient().
5172
5173
        """
5174
        return self._transient
5175
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5176
    def _transient_clean_rows_older_than(self, cr, seconds):
5177
        assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5178
        # Never delete rows used in last 5 minutes
4503.2.2 by Ronald Portier
[FIX] Correct both age based as count based vacuum cleaning.
5179
        seconds = max(seconds, 300)
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5180
        query = ("SELECT id FROM " + self._table + " WHERE"
5181
            " COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp"
5182
            " < ((now() at time zone 'UTC') - interval %s)")
5183
        cr.execute(query, ("%s seconds" % seconds,))
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5184
        ids = [x[0] for x in cr.fetchall()]
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5185
        self.unlink(cr, SUPERUSER_ID, ids)
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5186
4503.2.1 by Ronald Portier
[FIX] Fix vacuum cleaning both for time and count based cleaning,
5187
    def _transient_clean_old_rows(self, cr, max_count):
5188
        # Check how many rows we have in the table
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5189
        cr.execute("SELECT count(*) AS row_count FROM " + self._table)
4503.2.1 by Ronald Portier
[FIX] Fix vacuum cleaning both for time and count based cleaning,
5190
        res = cr.fetchall()
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5191
        if res[0][0] <= max_count:
4503.2.1 by Ronald Portier
[FIX] Fix vacuum cleaning both for time and count based cleaning,
5192
            return  # max not reached, nothing to do
4503.2.2 by Ronald Portier
[FIX] Correct both age based as count based vacuum cleaning.
5193
        self._transient_clean_rows_older_than(cr, 300)
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5194
5195
    def _transient_vacuum(self, cr, uid, force=False):
5196
        """Clean the transient records.
5197
5198
        This unlinks old records from the transient model tables whenever the
5199
        "_transient_max_count" or "_max_age" conditions (if any) are reached.
5200
        Actual cleaning will happen only once every "_transient_check_time" calls.
5201
        This means this method can be called frequently called (e.g. whenever
5202
        a new record is created).
4503.2.2 by Ronald Portier
[FIX] Correct both age based as count based vacuum cleaning.
5203
        Example with both max_hours and max_count active:
5204
        Suppose max_hours = 0.2 (e.g. 12 minutes), max_count = 20, there are 55 rows in the
5205
        table, 10 created/changed in the last 5 minutes, an additional 12 created/changed between
5206
        5 and 10 minutes ago, the rest created/changed more then 12 minutes ago.
5207
        - age based vacuum will leave the 22 rows created/changed in the last 12 minutes
5208
        - count based vacuum will wipe out another 12 rows. Not just 2, otherwise each addition
5209
          would immediately cause the maximum to be reached again.
5210
        - the 10 rows that have been created/changed the last 5 minutes will NOT be deleted
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5211
        """
5212
        assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5213
        _transient_check_time = 20          # arbitrary limit on vacuum executions
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5214
        self._transient_check_count += 1
4503.2.3 by Raphael Collet
[IMP] orm: fix vacuum of transient model
5215
        if not force and (self._transient_check_count < _transient_check_time):
4503.2.1 by Ronald Portier
[FIX] Fix vacuum cleaning both for time and count based cleaning,
5216
            return True  # no vacuum cleaning this time
5217
        self._transient_check_count = 0
3511.1.31 by Olivier Dony
[IMP] orm: fix Model hierarchy to avoid TransientModel leaking downstream
5218
5219
        # Age-based expiration
5220
        if self._transient_max_hours:
5221
            self._transient_clean_rows_older_than(cr, self._transient_max_hours * 60 * 60)
5222
5223
        # Count-based expiration
5224
        if self._transient_max_count:
5225
            self._transient_clean_old_rows(cr, self._transient_max_count)
5226
5227
        return True
5228
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5229
    def resolve_2many_commands(self, cr, uid, field_name, commands, fields=None, context=None):
5230
        """ Serializes one2many and many2many commands into record dictionaries
5231
            (as if all the records came from the database via a read()).  This
5232
            method is aimed at onchange methods on one2many and many2many fields.
5233
5234
            Because commands might be creation commands, not all record dicts
5235
            will contain an ``id`` field.  Commands matching an existing record
5236
            will have an ``id``.
5237
5238
            :param field_name: name of the one2many or many2many field matching the commands
5239
            :type field_name: str
5240
            :param commands: one2many or many2many commands to execute on ``field_name``
5241
            :type commands: list((int|False, int|False, dict|False))
5242
            :param fields: list of fields to read from the database, when applicable
5243
            :type fields: list(str)
5244
            :returns: records in a shape similar to that returned by ``read()``
5245
                (except records may be missing the ``id`` field if they don't exist in db)
5246
            :rtype: list(dict)
3747.1.1 by Xavier Morel
[ADD] method to correctly serialize o2m commands into a sequence of record-dicts for e.g. onchanges
5247
        """
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5248
        result = []             # result (list of dict)
4341.4.43 by Raphael Collet
[FIX] method resolve_2many_commands and its tests
5249
        record_ids = []         # ids of records to read
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5250
        updates = {}            # {id: dict} of updates on particular records
5251
5252
        for command in commands:
5253
            if not isinstance(command, (list, tuple)):
4341.4.43 by Raphael Collet
[FIX] method resolve_2many_commands and its tests
5254
                record_ids.append(command)
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5255
            elif command[0] == 0:
5256
                result.append(command[2])
5257
            elif command[0] == 1:
4341.4.43 by Raphael Collet
[FIX] method resolve_2many_commands and its tests
5258
                record_ids.append(command[1])
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5259
                updates.setdefault(command[1], {}).update(command[2])
5260
            elif command[0] in (2, 3):
4341.4.43 by Raphael Collet
[FIX] method resolve_2many_commands and its tests
5261
                record_ids = [id for id in record_ids if id != command[1]]
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5262
            elif command[0] == 4:
4341.4.43 by Raphael Collet
[FIX] method resolve_2many_commands and its tests
5263
                record_ids.append(command[1])
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5264
            elif command[0] == 5:
5265
                result, record_ids = [], []
5266
            elif command[0] == 6:
5267
                result, record_ids = [], list(command[2])
5268
5269
        # read the records and apply the updates
5270
        other_model = self.pool.get(self._all_columns[field_name].column._obj)
4341.4.43 by Raphael Collet
[FIX] method resolve_2many_commands and its tests
5271
        for record in other_model.read(cr, uid, record_ids, fields=fields, context=context):
4341.4.37 by Raphael Collet
[IMP] orm: fix and generalize method resolve_o2m_commands_to_record_dicts (now named resolve_2many_commands)
5272
            record.update(updates.get(record['id'], {}))
5273
            result.append(record)
5274
5275
        return result
5276
5277
    # for backward compatibility
4341.4.47 by Raphael Collet
[IMP] orm: simplify method definition
5278
    resolve_o2m_commands_to_record_dicts = resolve_2many_commands
3747.1.1 by Xavier Morel
[ADD] method to correctly serialize o2m commands into a sequence of record-dicts for e.g. onchanges
5279
4581.3.1 by Arnaud Pineux
[FIX] Automated action rules
5280
    def _register_hook(self, cr):
4632.1.3 by Raphael Collet
[IMP] register_hook: improve a bit the code
5281
        """ stuff to do right after the registry is built """
4581.3.1 by Arnaud Pineux
[FIX] Automated action rules
5282
        pass
5283
4769.2.2 by Vo Minh Thu
[IMP] orm: expose a signal_xxx() method to call workflow.trg_validate().
5284
    def __getattr__(self, name):
5285
        if name.startswith('signal_'):
5286
            signal_name = name[len('signal_'):]
5287
            assert signal_name
4769.2.7 by Raphael Collet
[IMP] remove model methods _workflow_trigger and _workflow_signal, and replace calls to new workflow methods
5288
            return (lambda *args, **kwargs:
5289
                    self.signal_workflow(*args, signal=signal_name, **kwargs))
4815.1.11 by Xavier Morel
[FIX] __getattr__ implementation on BaseModel
5290
        get = getattr(super(BaseModel, self), '__getattr__', None)
5291
        if get is not None: return get(name)
5292
        raise AttributeError(
5293
            "'%s' object has no attribute '%s'" % (type(self).__name__, name))
4769.2.2 by Vo Minh Thu
[IMP] orm: expose a signal_xxx() method to call workflow.trg_validate().
5294
3511.1.32 by Olivier Dony
[MERGE] sync w/ latest trunk (+ fix import cycles)
5295
# keep this import here, at top it will cause dependency cycle errors
5296
import expression
5297
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
5298
class Model(BaseModel):
5299
    """Main super-class for regular database-persisted OpenERP models.
5300
5301
    OpenERP models are created by inheriting from this class::
5302
5303
        class user(Model):
5304
            ...
5305
5306
    The system will later instantiate the class once per database (on
5307
    which the class' module is installed).
5308
    """
4066.18.1 by Olivier Dony
[FIX] orm: make sure all *Model classes define _auto,_register,_transient
5309
    _auto = True
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
5310
    _register = False # not visible in ORM registry, meant to be python-inherited only
3511.1.30 by Olivier Dony
[IMP] review/cleanup + fix TransientModels inheritance
5311
    _transient = False # True in a TransientModel
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
5312
5313
class TransientModel(BaseModel):
5314
    """Model super-class for transient records, meant to be temporarily
5315
       persisted, and regularly vaccuum-cleaned.
3747.1.8 by Olivier Dony
[IMP] orm: resolve_o2m_commands_...: simplify assert, remove context doc following guidelines
5316
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
5317
       A TransientModel has a simplified access rights management,
5318
       all users can create new records, and may only access the
5319
       records they created. The super-user has unrestricted access
5320
       to all TransientModel records.
5321
    """
4066.18.1 by Olivier Dony
[FIX] orm: make sure all *Model classes define _auto,_register,_transient
5322
    _auto = True
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
5323
    _register = False # not visible in ORM registry, meant to be python-inherited only
5324
    _transient = True
5325
5326
class AbstractModel(BaseModel):
5327
    """Abstract Model super-class for creating an abstract class meant to be
5328
       inherited by regular models (Models or TransientModels) but not meant to
5329
       be usable on its own, or persisted.
5330
5331
       Technical note: we don't want to make AbstractModel the super-class of
5332
       Model or BaseModel because it would not make sense to put the main
5333
       definition of persistence methods such as create() in it, and still we
5334
       should be able to override them within an AbstractModel.
5335
       """
5336
    _auto = False # don't create any database backend for AbstractModels
5337
    _register = False # not visible in ORM registry, meant to be python-inherited only
4066.18.1 by Olivier Dony
[FIX] orm: make sure all *Model classes define _auto,_register,_transient
5338
    _transient = False
3511.1.25 by Olivier Dony
[IMP] orm: introduce cleaner class hierarchy for models
5339
4408.2.1 by Xavier Morel
[ADD] big bit on new import: pretty much everything but o2m
5340
def itemgetter_tuple(items):
5341
    """ Fixes itemgetter inconsistency (useful in some cases) of not returning
5342
    a tuple if len(items) == 1: always returns an n-tuple where n = len(items)
5343
    """
5344
    if len(items) == 0:
5345
        return lambda a: ()
5346
    if len(items) == 1:
5347
        return lambda gettable: (gettable[items[0]],)
5348
    return operator.itemgetter(*items)
5349
class ImportWarning(Warning):
5350
    """ Used to send warnings upwards the stack during the import process
5351
    """
5352
    pass
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5353
5354
5355
def convert_pgerror_23502(model, fields, info, e):
5356
    m = re.match(r'^null value in column "(?P<field>\w+)" violates '
4408.2.17 by Xavier Morel
[FIX] postgres 9.2 added data at the end of 23502's error message, only use prefix matches not fullstring
5357
                 r'not-null constraint\n',
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5358
                 str(e))
4553 by Xavier Morel
[FIX] temperate error message in case of missing required field
5359
    field_name = m.group('field')
5360
    if not m or field_name not in fields:
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5361
        return {'message': unicode(e)}
4553 by Xavier Morel
[FIX] temperate error message in case of missing required field
5362
    message = _(u"Missing required value for the field '%s'.") % field_name
5363
    field = fields.get(field_name)
5364
    if field:
4805 by Fabien Pinckaers
[iMP] error message simplified for required values
5365
        message = _(u"Missing required value for the field '%s' (%s)") % (field['string'], field_name)
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5366
    return {
4553 by Xavier Morel
[FIX] temperate error message in case of missing required field
5367
        'message': message,
5368
        'field': field_name,
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5369
    }
4743.1.89 by Xavier Morel
[IMP] don't log from import when converting psycopg exceptions to output messages
5370
def convert_pgerror_23505(model, fields, info, e):
5371
    m = re.match(r'^duplicate key (?P<field>\w+) violates unique constraint',
5372
                 str(e))
5373
    field_name = m.group('field')
5374
    if not m or field_name not in fields:
5375
        return {'message': unicode(e)}
5376
    message = _(u"The value for the field '%s' already exists.") % field_name
5377
    field = fields.get(field_name)
5378
    if field:
5379
        message = _(u"%s This might be '%s' in the current model, or a field "
5380
                    u"of the same name in an o2m.") % (message, field['string'])
5381
    return {
5382
        'message': message,
5383
        'field': field_name,
5384
    }
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5385
5386
PGERROR_TO_OE = collections.defaultdict(
5387
    # shape of mapped converters
5388
    lambda: (lambda model, fvg, info, pgerror: {'message': unicode(pgerror)}), {
5389
    # not_null_violation
5390
    '23502': convert_pgerror_23502,
4743.1.89 by Xavier Morel
[IMP] don't log from import when converting psycopg exceptions to output messages
5391
    # unique constraint error
5392
    '23505': convert_pgerror_23505,
4408.2.13 by Xavier Morel
[ADD] ability to convert postgres error messages to human-readable ones
5393
})
3668 by Vo Minh Thu
[IMP] orm: expose ir.model.access check shortcuts.
5394
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: