[MERGE] from trunk

bzr revid: rco@openerp.com-20110718083252-4phjgbo7fbxoeots
This commit is contained in:
Raphael Collet
2011-07-18 10:32:52 +02:00
10 changed files with 258 additions and 159 deletions
+21 -16
View File
@@ -1,20 +1,21 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * base
# * base
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2010-06-06 04:19+0000\n"
"Last-Translator: Vladimír Burian <vladimir.burian@email.cz>\n"
"PO-Revision-Date: 2011-07-15 18:58+0000\n"
"Last-Translator: Chronos <robie@centrum.cz>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-07-02 04:55+0000\n"
"X-Generator: Launchpad (build 13168)\n"
"X-Launchpad-Export-Date: 2011-07-16 04:53+0000\n"
"X-Generator: Launchpad (build 13405)\n"
"X-Poedit-Language: Czech\n"
#. module: base
#: view:ir.filters:0
@@ -585,7 +586,7 @@ msgstr "Srbsko"
#. module: base
#: selection:ir.translation,type:0
msgid "Wizard View"
msgstr ""
msgstr "Zobrazení průvodce"
#. module: base
#: model:res.country,name:base.kh
@@ -1181,7 +1182,7 @@ msgstr "res.request.link"
#. module: base
#: field:ir.actions.wizard,name:0
msgid "Wizard Info"
msgstr ""
msgstr "Informace průvodce"
#. module: base
#: view:base.language.export:0
@@ -1196,6 +1197,8 @@ msgid ""
"Do not display this log if it belongs to the same object the user is working "
"on"
msgstr ""
"Nezobrazovat tento záznam, pokud patří ke stejnému objektu, se kterým "
"pracuje uživatel"
#. module: base
#: model:res.country,name:base.tp
@@ -1253,7 +1256,7 @@ msgstr "Maledivy"
#. module: base
#: help:ir.values,res_id:0
msgid "Keep 0 if the action must appear on all resources."
msgstr ""
msgstr "Nechejte 0 pokud se musí akce objevit u všech zdrojů."
#. module: base
#: model:ir.model,name:base.model_ir_rule
@@ -2501,7 +2504,7 @@ msgstr "Kontrola Ean"
#. module: base
#: field:res.partner,vat:0
msgid "VAT"
msgstr "DPH"
msgstr "DIČ"
#. module: base
#: view:res.lang:0
@@ -3588,7 +3591,7 @@ msgstr ""
#. module: base
#: model:ir.ui.menu,name:base.menu_translation_app
msgid "Application Terms"
msgstr ""
msgstr "Podmínky aplikace"
#. module: base
#: help:res.config.users,context_tz:0
@@ -3818,7 +3821,7 @@ msgstr "Vlastník bankovního účtu"
#. module: base
#: model:ir.actions.act_window,name:base.act_values_form
msgid "Client Actions Connections"
msgstr ""
msgstr "Nastavení akcí klienta"
#. module: base
#: field:ir.attachment,res_name:0
@@ -3880,7 +3883,7 @@ msgstr "Měsíc"
#. module: base
#: model:res.country,name:base.my
msgid "Malaysia"
msgstr ""
msgstr "Malaysie"
#. module: base
#: view:base.language.install:0
@@ -4282,7 +4285,7 @@ msgstr "Zděděný pohled"
#. module: base
#: view:ir.translation:0
msgid "Source Term"
msgstr ""
msgstr "Podmínky zdrojového kódu"
#. module: base
#: model:ir.ui.menu,name:base.menu_main_pm
@@ -7262,6 +7265,8 @@ msgid ""
"Changing the type of a column is not yet supported. Please drop it and "
"create it again!"
msgstr ""
"Změna typu sloupce ještě není podporována. Prosíme smažte jej a opět "
"vytvořte!"
#. module: base
#: field:ir.ui.view_sc,user_id:0
@@ -8821,7 +8826,7 @@ msgstr "base.module.configuration"
#: field:base.language.export,name:0
#: field:ir.attachment,datas_fname:0
msgid "Filename"
msgstr ""
msgstr "Jméno souboru"
#. module: base
#: field:ir.model,access_ids:0
@@ -8899,7 +8904,7 @@ msgstr "Bulharština / български език"
#. module: base
#: model:ir.ui.menu,name:base.menu_aftersale
msgid "After-Sale Services"
msgstr ""
msgstr "Poprodejní služby"
#. module: base
#: view:ir.actions.todo:0
+6 -4
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-06-06 17:53+0000\n"
"Last-Translator: Hector Rojas (doingIT.cl) <Unknown>\n"
"PO-Revision-Date: 2011-07-11 21:37+0000\n"
"Last-Translator: doingit.cl <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-07-02 05:03+0000\n"
"X-Launchpad-Export-Date: 2011-07-12 04:35+0000\n"
"X-Generator: Launchpad (build 13168)\n"
#. module: base
@@ -47,6 +47,8 @@ msgid ""
"The second argument of the many2many field %s must be a SQL table !You used "
"%s, which is not a valid SQL table name."
msgstr ""
"¡El segundo argumento del campo many2many %s debe ser una tabla SQL! Has "
"utilizado %s, que no es un nombre de tabla SQL válido."
#. module: base
#: view:ir.values:0
@@ -278,7 +280,7 @@ msgstr "Nombre de asistente"
#: code:addons/orm.py:2160
#, python-format
msgid "Invalid group_by"
msgstr ""
msgstr "group_by Invalido"
#. module: base
#: field:res.partner,credit_limit:0
@@ -62,7 +62,7 @@ class ir_config_parameter(osv.osv):
if not ids:
self.set_param(cr, 1, key, func())
def get_param(self, cr, uid, key, context=None):
def get_param(self, cr, uid, key, default=False, context=None):
""" Get the value of a parameter.
@param key: The key of the parameter.
@@ -72,7 +72,7 @@ class ir_config_parameter(osv.osv):
"""
ids = self.search(cr, uid, [('key','=',key)], context=context)
if not ids:
return False
return default
param = self.browse(cr, uid, ids[0], context=context)
value = param.value
return value
-7
View File
@@ -131,13 +131,6 @@ class res_currency(osv.osv):
else:
return (from_amount * rate)
def name_search(self, cr, uid, name, args=[], operator='ilike', context={}, limit=100):
args2 = args[:]
if name:
args += [('name', operator, name)]
ids = self.search(cr, uid, args, limit=limit)
res = self.name_get(cr, uid, ids, context)
return res
res_currency()
class res_currency_rate(osv.osv):
+1
View File
@@ -354,6 +354,7 @@
<rng:element name="separator">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:optional><rng:attribute name="name"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="rowspan"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional>
+62 -57
View File
@@ -88,14 +88,14 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if new_query:
cr.execute(new_query)
def load_init_xml(cr, m, idref, mode):
_load_data(cr, m, idref, mode, 'init_xml')
def load_init_xml(cr, module_name, idref, mode):
_load_data(cr, module_name, idref, mode, 'init_xml')
def load_update_xml(cr, m, idref, mode):
_load_data(cr, m, idref, mode, 'update_xml')
def load_update_xml(cr, module_name, idref, mode):
_load_data(cr, module_name, idref, mode, 'update_xml')
def load_demo_xml(cr, m, idref, mode):
_load_data(cr, m, idref, mode, 'demo_xml')
def load_demo_xml(cr, module_name, idref, mode):
_load_data(cr, module_name, idref, mode, 'demo_xml')
def load_data(cr, module_name, idref, mode):
_load_data(cr, module_name, idref, mode, 'data')
@@ -152,38 +152,38 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
status = {}
processed_modules = []
loaded_modules = []
statusi = 0
pool = pooler.get_pool(cr.dbname)
migrations = openerp.modules.migration.MigrationManager(cr, graph)
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
# register, instanciate and initialize models for each modules
# register, instantiate and initialize models for each modules
for package in graph:
if skip_modules and package.name in skip_modules:
module_name = package.name
module_id = package.id
if skip_modules and module_name in skip_modules:
continue
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
migrations.migrate_module(package, 'pre')
register_module_classes(package.name)
models = pool.instanciate(package.name, cr)
loaded_modules.append(package.name)
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
init_module_models(cr, package.name, models)
cr.commit()
# load data for each modules
modobj = pool.get('ir.module.module')
for package in graph:
status['progress'] = (float(statusi)+0.1) / len(graph)
m = package.name
mid = package.id
status['progress'] = float(statusi) / len(graph)
if skip_modules and m in skip_modules:
continue
# Can't put this line out of the loop: ir.module.module will be
# registered by init_module_models() above.
modobj = pool.get('ir.module.module')
if perform_checks:
modobj.check(cr, 1, [mid])
modobj.check(cr, 1, [module_id])
idref = {}
status['progress'] = (float(statusi)+0.4) / len(graph)
mode = 'update'
if hasattr(package, 'init') or package.state == 'to install':
@@ -192,21 +192,21 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
load_init_xml(cr, m, idref, mode)
load_update_xml(cr, m, idref, mode)
load_data(cr, m, idref, mode)
modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
load_init_xml(cr, module_name, idref, mode)
load_update_xml(cr, module_name, idref, mode)
load_data(cr, module_name, idref, mode)
if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
status['progress'] = (float(statusi)+0.75) / len(graph)
load_demo_xml(cr, m, idref, mode)
load_demo(cr, m, idref, mode)
cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
load_demo_xml(cr, module_name, idref, mode)
load_demo(cr, module_name, idref, mode)
cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
# launch tests only in demo mode, as most tests will depend
# on demo data. Other tests can be added into the regular
# 'data' section, but should probably not alter the data,
# as there is no rollback.
load_test(cr, m, idref, mode)
load_test(cr, module_name, idref, mode)
processed_modules.append(package.name)
@@ -214,22 +214,21 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
ver = release.major_version + '.' + package.data['version']
# Set new modules and dependencies
modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
cr.commit()
modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
# Update translations for all installed languages
modobj.update_translations(cr, 1, [mid], None)
cr.commit()
modobj.update_translations(cr, 1, [module_id], None)
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
if hasattr(package, kind):
delattr(package, kind)
cr.commit()
statusi += 1
cr.commit()
return processed_modules
return loaded_modules, processed_modules
def _check_module_names(cr, module_names):
mod_names = set(module_names)
@@ -245,11 +244,26 @@ def _check_module_names(cr, module_names):
incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules):
"""Loads modules marked with ``states``, adding them to ``graph`` and
``loaded_modules`` and returns a list of installed/upgraded modules."""
processed_modules = []
while True:
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
module_list = [name for (name,) in cr.fetchall() if name not in graph]
new_modules_in_graph = graph.add_modules(cr, module_list, force)
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
processed_modules.extend(processed)
loaded_modules.extend(loaded)
if not processed: break
return processed_modules
def load_modules(db, force_demo=False, status=None, update_module=False):
# TODO status['progress'] reporting is broken: used twice (and reset each
# time to zero) in load_module_graph, not fine-grained enough.
# It should be a method exposed by the pool.
initialize_sys_path()
open_openerp_namespace()
@@ -271,10 +285,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# This is a brand new pool, just created in pooler.get_db_and_pool()
pool = pooler.get_pool(cr.dbname)
processed_modules = []
processed_modules = [] # for cleanup step after install
loaded_modules = [] # to avoid double loading
report = tools.assertion_report()
# NOTE: Try to also load the modules that have been marked as uninstallable previously...
STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
if 'base' in tools.config['update'] or 'all' in tools.config['update']:
cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
@@ -284,7 +297,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
if not graph:
logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
loaded, processed = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
processed_modules.extend(processed)
if tools.config['load_language']:
for lang in tools.config['load_language'].split(','):
@@ -313,28 +327,19 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
STATES_TO_LOAD += ['to install']
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
loop_guardrail = 0
while True:
loop_guardrail += 1
if loop_guardrail > 100:
raise ValueError('Possible recursive module tree detected, aborting.')
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
module_list = [name for (name,) in cr.fetchall() if name not in graph]
if not module_list:
break
new_modules_in_graph = graph.add_modules(cr, module_list, force)
if new_modules_in_graph == 0:
# nothing to load
break
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
# IMPORTANT: this is done in two parts, first loading all installed or
# partially installed modules (i.e. installed/to upgrade), to
# offer a consistent system to the second part: installing
# newly selected modules.
states_to_load = ['installed', 'to upgrade']
processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
processed_modules.extend(processed)
if update_module:
states_to_load = ['to install']
processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
processed_modules.extend(processed)
# load custom models
cr.execute('select model from ir_model where state=%s', ('manual',))
+47 -51
View File
@@ -1212,34 +1212,30 @@ class serialized(_column):
self._symbol_get = self._deserialize_func
super(serialized, self).__init__(string=string, **args)
# TODO: review completly this class for speed improvement
class property(function):
def _get_default(self, obj, cr, uid, prop_name, context=None):
return self._get_defaults(obj, cr, uid, [prop_name], context=None)[0][prop_name]
return self._get_defaults(obj, cr, uid, [prop_name], context=None)[prop_name]
def _get_defaults(self, obj, cr, uid, prop_name, context=None):
def _get_defaults(self, obj, cr, uid, prop_names, context=None):
"""Get the default values for ``prop_names´´ property fields (result of ir.property.get() function for res_id = False).
:param list of string prop_names: list of name of property fields for those we want the default value
:return: map of property field names to their default value
:rtype: dict
"""
prop = obj.pool.get('ir.property')
domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)]
ids = prop.search(cr, uid, domain, context=context)
replaces = {}
default_value = {}.fromkeys(prop_name, False)
for prop_rec in prop.browse(cr, uid, ids, context=context):
if default_value.get(prop_rec.fields_id.name, False):
continue
value = prop.get_by_record(cr, uid, prop_rec, context=context) or False
default_value[prop_rec.fields_id.name] = value
if value and (prop_rec.type == 'many2one'):
replaces.setdefault(value._name, {})
replaces[value._name][value.id] = True
return default_value, replaces
res = {}
for prop_name in prop_names:
res[prop_name] = prop.get(cr, uid, prop_name, obj._name, context=context)
return res
def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
prop = obj.pool.get('ir.property')
vids = [obj._name + ',' + str(oid) for oid in ids]
domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
domain = [('fields_id.model', '=', obj._name), ('fields_id.name', 'in', prop_name)]
#domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
if vids:
domain = [('res_id', 'in', vids)] + domain
@@ -1274,48 +1270,48 @@ class property(function):
}, context=context)
return False
def _fnct_read(self, obj, cr, uid, ids, prop_names, obj_dest, context=None):
prop = obj.pool.get('ir.property')
# get the default values (for res_id = False) for the property fields
default_val = self._get_defaults(obj, cr, uid, prop_names, context)
def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
properties = obj.pool.get('ir.property')
domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in ids])]
nids = properties.search(cr, uid, domain, context=context)
default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context)
# build the dictionary that will be returned
res = {}
for id in ids:
res[id] = default_val.copy()
brs = properties.browse(cr, uid, nids, context=context)
for prop in brs:
value = properties.get_by_record(cr, uid, prop, context=context)
res[prop.res_id.id][prop.fields_id.name] = value or False
if value and (prop.type == 'many2one'):
# check existence as root, as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access.
record_exists = obj.pool.get(value._name).exists(cr, 1, value.id)
if record_exists:
replaces.setdefault(value._name, {})
replaces[value._name][value.id] = True
else:
res[prop.res_id.id][prop.fields_id.name] = False
for rep in replaces:
# search+name_get as root, as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access.
nids = obj.pool.get(rep).search(cr, 1, [('id','in',replaces[rep].keys())], context=context)
replaces[rep] = dict(obj.pool.get(rep).name_get(cr, 1, nids, context=context))
for prop in prop_name:
for prop_name in prop_names:
property_field = obj._all_columns.get(prop_name).column
property_destination_obj = property_field._obj if property_field._type == 'many2one' else False
# If the property field is a m2o field, we will append the id of the value to name_get_ids
# in order to make a name_get in batch for all the ids needed.
name_get_ids = {}
for id in ids:
if res[id][prop] and hasattr(res[id][prop], '_name'):
res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False))
# get the result of ir.property.get() for this res_id and save it in res if it's existing
obj_reference = obj._name + ',' + str(id)
value = prop.get(cr, uid, prop_name, obj._name, res_id=obj_reference, context=context)
if value:
res[id][prop_name] = value
# Check existence as root (as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access) in order to avoid
# pointing on an unexisting record.
if property_destination_obj:
if res[id][prop_name] and obj.pool.get(property_destination_obj).exists(cr, 1, res[id][prop_name].id):
name_get_ids[id] = res[id][prop_name].id
else:
res[id][prop_name] = False
if property_destination_obj:
# name_get as root (as seeing the name of a related
# object depends on access right of source document,
# not target, so user may not have access.)
name_get_values = dict(obj.pool.get(property_destination_obj).name_get(cr, 1, name_get_ids.values(), context=context))
# the property field is a m2o, we need to return a tuple with (id, name)
for k, v in name_get_ids.iteritems():
if res[k][prop_name]:
res[k][prop_name] = (v , name_get_values.get(v))
return res
def _field_get(self, cr, uid, model_name, prop):
if not self.field_id.get(cr.dbname):
cr.execute('SELECT id \
+19 -6
View File
@@ -77,9 +77,21 @@ module_class_list = {}
ROOT_USER_ID = 1
def transfer_field_to_modifiers(field, modifiers):
for a in ('invisible', 'readonly', 'required'):
if field.get(a):
modifiers[a] = bool(field.get(a))
default_values = {}
state_exceptions = {}
for attr in ('invisible', 'readonly', 'required'):
state_exceptions[attr] = []
default_values[attr] = bool(field.get(attr))
for state, modifs in (field.get("states",{})).items():
for modif in modifs:
if default_values[modif[0]] != modif[1]:
state_exceptions[modif[0]].append(state)
for attr, default_value in default_values.items():
if state_exceptions[attr]:
modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])]
else:
modifiers[attr] = default_value
# Don't deal with groups, it is done by check_group().
@@ -2484,9 +2496,10 @@ class orm(orm_template):
:param int offset: optional number of records to skip
:param int limit: optional max number of records to return
:param dict context: context arguments, like lang, time zone
:param order: optional ``order by`` specification, for overriding the natural
sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
(supported only for many2one fields currently)
:param list orderby: optional ``order by`` specification, for
overriding the natural sort ordering of the
groups, see also :py:meth:`~osv.osv.osv.search`
(supported only for many2one fields currently)
:return: list of dictionaries(one dictionary for each record) containing:
* the values of fields grouped by the fields in ``groupby`` argument
+85 -16
View File
@@ -37,7 +37,8 @@ __all__ = ['db_connect', 'close_db']
from threading import currentThread
import logging
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE,\
ISOLATION_LEVEL_REPEATABLE_READ
from psycopg2.psycopg1 import cursor as psycopg1cursor
from psycopg2.pool import PoolError
@@ -79,12 +80,64 @@ re_into = re.compile('.* into "?([a-zA-Z_0-9]+)"? .*$');
sql_counter = 0
class Cursor(object):
""" Cursor is an open transaction to Postgres, utilizing a TCP connection
A lightweight wrapper around psycopg2's `psycopg1cursor` objects
This is the object behind the `cr` variable used all over the OpenERP
code.
"""Represents an open transaction to the PostgreSQL DB backend,
acting as a lightweight wrapper around psycopg2's
``psycopg1cursor`` objects.
``Cursor`` is the object behind the ``cr`` variable used all
over the OpenERP code.
.. rubric:: Transaction Isolation
One very important property of database transactions is the
level of isolation between concurrent transactions.
The SQL standard defines four levels of transaction isolation,
ranging from the most strict *Serializable* level, to the least
strict *Read Uncommitted* level. These levels are defined in
terms of the phenomena that must not occur between concurrent
transactions, such as *dirty read*, etc.
In the context of a generic business data management software
such as OpenERP, we need the best guarantees that no data
corruption can ever be cause by simply running multiple
transactions in parallel. Therefore, the preferred level would
be the *serializable* level, which ensures that a set of
transactions is guaranteed to produce the same effect as
running them one at a time in some order.
However, most database management systems implement a limited
serializable isolation in the form of
`snapshot isolation <http://en.wikipedia.org/wiki/Snapshot_isolation>`_,
providing most of the same advantages as True Serializability,
with a fraction of the performance cost.
With PostgreSQL up to version 9.0, this snapshot isolation was
the implementation of both the ``REPEATABLE READ`` and
``SERIALIZABLE`` levels of the SQL standard.
As of PostgreSQL 9.1, the previous snapshot isolation implementation
was kept for ``REPEATABLE READ``, while a new ``SERIALIZABLE``
level was introduced, providing some additional heuristics to
detect a concurrent update by parallel transactions, and forcing
one of them to rollback.
OpenERP implements its own level of locking protection
for transactions that are highly likely to provoke concurrent
updates, such as stock reservations or document sequences updates.
Therefore we mostly care about the properties of snapshot isolation,
but we don't really need additional heuristics to trigger transaction
rollbacks, as we are taking care of triggering instant rollbacks
ourselves when it matters (and we can save the additional performance
hit of these heuristics).
As a result of the above, we have selected ``REPEATABLE READ`` as
the default transaction isolation level for OpenERP cursors, as
it will be mapped to the desired ``snapshot isolation`` level for
all supported PostgreSQL version (8.3 - 9.x).
Note: up to psycopg2 v.2.4.2, psycopg2 itself remapped the repeatable
read level to serializable before sending it to the database, so it would
actually select the new serializable mode on PostgreSQL 9.1. Make
sure you use psycopg2 v2.4.2 or newer if you use PostgreSQL 9.1 and
the performance hit is a concern for you.
"""
IN_MAX = 1000 # decent limit on size of IN queries - guideline = Oracle limit
__logger = None
@@ -100,7 +153,7 @@ class Cursor(object):
return f(self, *args, **kwargs)
return wrapper
def __init__(self, pool, dbname, serialized=False):
def __init__(self, pool, dbname, serialized=True):
if self.__class__.__logger is None:
self.__class__.__logger = logging.getLogger('db.cursor')
self.sql_from_log = {}
@@ -115,7 +168,11 @@ class Cursor(object):
# is raised by any of the following initialisations
self._pool = pool
self.dbname = dbname
# Whether to enable snapshot isolation level for this cursor.
# see also the docstring of Cursor.
self._serialized = serialized
self._cnx = pool.borrow(dsn(dbname))
self._obj = self._cnx.cursor(cursor_factory=psycopg1cursor)
self.__closed = False # real initialisation value
@@ -227,9 +284,6 @@ class Cursor(object):
self.__closer = frame_codeinfo(currentframe(),3)
self.print_log()
if not self._serialized:
self.rollback() # Ensure we close the current transaction.
self._obj.close()
# This force the cursor to be freed, and thus, available again. It is
@@ -248,8 +302,23 @@ class Cursor(object):
@check
def autocommit(self, on):
offlevel = [ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE][bool(self._serialized)]
self._cnx.set_isolation_level([offlevel, ISOLATION_LEVEL_AUTOCOMMIT][bool(on)])
if on:
isolation_level = ISOLATION_LEVEL_AUTOCOMMIT
else:
# If a serializable cursor was requested, we
# use the appropriate PotsgreSQL isolation level
# that maps to snaphsot isolation.
# For all supported PostgreSQL versions (8.3-9.x),
# this is currently the ISOLATION_REPEATABLE_READ.
# See also the docstring of this class.
# NOTE: up to psycopg 2.4.2, repeatable read
# is remapped to serializable before being
# sent to the database, so it is in fact
# unavailable for use with pg 9.1.
isolation_level = ISOLATION_LEVEL_REPEATABLE_READ \
if self._serialized \
else ISOLATION_LEVEL_READ_COMMITTED
self._cnx.set_isolation_level(isolation_level)
@check
def commit(self):
@@ -383,13 +452,13 @@ class Connection(object):
self.dbname = dbname
self._pool = pool
def cursor(self, serialized=False):
def cursor(self, serialized=True):
cursor_type = serialized and 'serialized ' or ''
self.__logger.log(logging.DEBUG_SQL, 'create %scursor to %r', cursor_type, self.dbname)
return Cursor(self._pool, self.dbname, serialized=serialized)
def serialized_cursor(self):
return self.cursor(True)
# serialized_cursor is deprecated - cursors are serialized by default
serialized_cursor = cursor
def __nonzero__(self):
"""Check if connection is possible"""
+15
View File
@@ -1387,6 +1387,21 @@ def upload_data(email, data, type='SURVEY'):
a.start()
return True
def get_and_group_by_field(cr, uid, obj, ids, field, context=None):
""" Read the values of ``field´´ for the given ``ids´´ and group ids by value.
:param string field: name of the field we want to read and group by
:return: mapping of field values to the list of ids that have it
:rtype: dict
"""
res = {}
for record in obj.read(cr, uid, ids, [field], context=context):
key = record[field]
res.setdefault(key[0] if isinstance(key, tuple) else key, []).append(record['id'])
return res
def get_and_group_by_company(cr, uid, obj, ids, context=None):
return get_and_group_by_field(cr, uid, obj, ids, field='company_id', context=context)
# port of python 2.6's attrgetter with support for dotted notation
def resolve_attr(obj, attr):