diff --git a/debian/control b/debian/control
index d4cf0c3f45e..a4c1b050c0a 100644
--- a/debian/control
+++ b/debian/control
@@ -19,6 +19,7 @@ Depends:
python-docutils,
python-feedparser,
python-gdata,
+ python-imaging,
python-jinja2,
python-ldap,
python-libxslt1,
@@ -46,7 +47,7 @@ Depends:
Conflicts: tinyerp-server, openerp-server, openerp-web
Replaces: tinyerp-server, openerp-server, openerp-web
Recommends:
- graphviz, ghostscript, postgresql, python-imaging, python-matplotlib
+ graphviz, ghostscript, postgresql, python-matplotlib, poppler-utils
Description: OpenERP Enterprise Resource Management
OpenERP, previously known as TinyERP, is a complete ERP and CRM. The main
features are accounting (analytic and financial), stock management, sales and
diff --git a/debian/openerp.init b/debian/openerp.init
index 6abb6f10ed6..98e653298b2 100644
--- a/debian/openerp.init
+++ b/debian/openerp.init
@@ -17,55 +17,46 @@ DAEMON=/usr/bin/openerp-server
NAME=openerp-server
DESC=openerp-server
CONFIG=/etc/openerp/openerp-server.conf
-LOGFILE=/var/log/openerp-server.log
+LOGFILE=/var/log/openerp/openerp-server.log
USER=openerp
test -x ${DAEMON} || exit 0
set -e
+do_start () {
+ echo -n "Starting ${DESC}: "
+ start-stop-daemon --start --quiet --pidfile /var/run/${NAME}.pid --chuid ${USER} --background --make-pidfile --exec ${DAEMON} -- --config=${CONFIG} --logfile=${LOGFILE}
+ echo "${NAME}."
+}
+
+do_stop () {
+ echo -n "Stopping ${DESC}: "
+ start-stop-daemon --stop --quiet --pidfile /var/run/${NAME}.pid --oknodo
+ echo "${NAME}."
+}
+
case "${1}" in
- start)
- echo -n "Starting ${DESC}: "
+ start)
+ do_start
+ ;;
- start-stop-daemon --start --quiet --pidfile /var/run/${NAME}.pid \
- --chuid ${USER} --background --make-pidfile \
- --exec ${DAEMON} -- --config=${CONFIG} \
- --logfile=${LOGFILE}
+ stop)
+ do_stop
+ ;;
- echo "${NAME}."
- ;;
+ restart|force-reload)
+ echo -n "Restarting ${DESC}: "
+ do_stop
+ sleep 1
+ do_start
+ ;;
- stop)
- echo -n "Stopping ${DESC}: "
-
- start-stop-daemon --stop --quiet --pidfile /var/run/${NAME}.pid \
- --oknodo
-
- echo "${NAME}."
- ;;
-
- restart|force-reload)
- echo -n "Restarting ${DESC}: "
-
- start-stop-daemon --stop --quiet --pidfile /var/run/${NAME}.pid \
- --oknodo
-
- sleep 1
-
- start-stop-daemon --start --quiet --pidfile /var/run/${NAME}.pid \
- --chuid ${USER} --background --make-pidfile \
- --exec ${DAEMON} -- --config=${CONFIG} \
- --logfile=${LOGFILE}
-
- echo "${NAME}."
- ;;
-
- *)
- N=/etc/init.d/${NAME}
- echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2
- exit 1
- ;;
+ *)
+ N=/etc/init.d/${NAME}
+ echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
esac
exit 0
diff --git a/debian/openerp.postinst b/debian/openerp.postinst
index 8700a259198..2eccd5111ec 100644
--- a/debian/openerp.postinst
+++ b/debian/openerp.postinst
@@ -12,9 +12,9 @@ case "${1}" in
chown openerp:openerp /etc/openerp/openerp-server.conf
chmod 0640 /etc/openerp/openerp-server.conf
# Creating log file
- touch /var/log/openerp-server.log
- chown openerp:openerp /var/log/openerp-server.log
- chmod 0640 /var/log/openerp-server.log
+ mkdir -p /var/log/openerp/
+ chown openerp:openerp /var/log/openerp
+ chmod 0750 /var/log/openerp
# Creating local storage directory
mkdir -p /var/lib/openerp/filestore
chown openerp:openerp -R /var/lib/openerp
diff --git a/openerp/__init__.py b/openerp/__init__.py
index 960890de0b6..111677cd707 100644
--- a/openerp/__init__.py
+++ b/openerp/__init__.py
@@ -22,6 +22,17 @@
""" OpenERP core library.
"""
+
+# Make sure the OpenERP server runs in UTC. This is especially necessary
+# under Windows as under Linux it seems the real import of time is
+# sufficiently deferred so that setting the TZ environment variable
+# in openerp.cli.server was working.
+import os
+os.environ['TZ'] = 'UTC' # Set the timezone...
+import time # ... *then* import time.
+del os
+del time
+
# The hard-coded super-user id (a.k.a. administrator, or root user).
SUPERUSER_ID = 1
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index cde6f6f9a91..c80a20e40e5 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -632,7 +632,7 @@ class actions_server(osv.osv):
.read(cr, uid, action.action_id.id, context=context)
if action.state=='code':
- eval(action.code, cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
+ eval(action.code.strip(), cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
if 'action' in cxt:
return cxt['action']
diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py
index 1cd9ed94d4c..3bf0dc3ae24 100644
--- a/openerp/addons/base/ir/ir_attachment.py
+++ b/openerp/addons/base/ir/ir_attachment.py
@@ -83,7 +83,7 @@ class ir_attachment(osv.osv):
if bin_size:
r = os.path.getsize(full_path)
else:
- r = open(full_path).read().encode('base64')
+ r = open(full_path,'rb').read().encode('base64')
except IOError:
_logger.error("_read_file reading %s",full_path)
return r
diff --git a/openerp/addons/base/ir/ir_cron.py b/openerp/addons/base/ir/ir_cron.py
index c3f7ccc7dd2..103151f32de 100644
--- a/openerp/addons/base/ir/ir_cron.py
+++ b/openerp/addons/base/ir/ir_cron.py
@@ -18,8 +18,9 @@
# along with this program. If not, see .
#
##############################################################################
-import time
import logging
+import threading
+import time
import psycopg2
from datetime import datetime
from dateutil.relativedelta import relativedelta
@@ -188,6 +189,7 @@ class ir_cron(osv.osv):
If a job was processed, returns True, otherwise returns False.
"""
db = openerp.sql_db.db_connect(db_name)
+ threading.current_thread().dbname = db_name
cr = db.cursor()
jobs = []
try:
@@ -242,6 +244,9 @@ class ir_cron(osv.osv):
# we're exiting due to an exception while acquiring the lock
lock_cr.close()
+ if hasattr(threading.current_thread(), 'dbname'): # cron job could have removed it as side-effect
+ del threading.current_thread().dbname
+
def _try_lock(self, cr, uid, ids, context=None):
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
to make sure a following write() or unlink() will not block due
diff --git a/openerp/addons/base/ir/ir_filters.py b/openerp/addons/base/ir/ir_filters.py
index 74665a1ffe3..f302fe5c7bf 100644
--- a/openerp/addons/base/ir/ir_filters.py
+++ b/openerp/addons/base/ir/ir_filters.py
@@ -28,7 +28,7 @@ class ir_filters(osv.osv):
_description = 'Filters'
def _list_all_models(self, cr, uid, context=None):
- cr.execute("SELECT model, name from ir_model")
+ cr.execute("SELECT model, name FROM ir_model ORDER BY name")
return cr.fetchall()
def copy(self, cr, uid, id, default=None, context=None):
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index f0bacc1cd32..7bb22881273 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -25,6 +25,7 @@ import time
import types
import openerp
+import openerp.modules.registry
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.osv import fields,osv
@@ -168,7 +169,9 @@ class ir_model(osv.osv):
if not context.get(MODULE_UNINSTALL_FLAG):
# only reload pool for normal unlink. For module uninstall the
# reload is done independently in openerp.modules.loading
+ cr.commit() # must be committed before reloading registry in new cursor
openerp.modules.registry.RegistryManager.new(cr.dbname)
+ openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res
@@ -194,6 +197,7 @@ class ir_model(osv.osv):
field_state='manual',
select=vals.get('select_level', '0'))
self.pool[vals['model']]._auto_init(cr, ctx)
+ openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res
def instanciate(self, cr, user, model, context=None):
@@ -259,7 +263,6 @@ class ir_model_fields(osv.osv):
'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
'on_delete': 'set null',
'select_level': '0',
- 'size': 64,
'field_description': '',
'selectable': 1,
}
@@ -289,10 +292,10 @@ class ir_model_fields(osv.osv):
return True
def _size_gt_zero_msg(self, cr, user, ids, context=None):
- return _('Size of the field can never be less than 1 !')
+ return _('Size of the field can never be less than 0 !')
_sql_constraints = [
- ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
+ ('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
]
def _drop_column(self, cr, uid, ids, context=None):
@@ -318,6 +321,9 @@ class ir_model_fields(osv.osv):
self._drop_column(cr, user, ids, context)
res = super(ir_model_fields, self).unlink(cr, user, ids, context)
+ if not context.get(MODULE_UNINSTALL_FLAG):
+ cr.commit()
+ openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res
def create(self, cr, user, vals, context=None):
@@ -349,6 +355,7 @@ class ir_model_fields(osv.osv):
select=vals.get('select_level', '0'),
update_custom_fields=True)
self.pool[vals['model']]._auto_init(cr, ctx)
+ openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res
@@ -465,6 +472,7 @@ class ir_model_fields(osv.osv):
for col_name, col_prop, val in patch_struct[1]:
setattr(obj._columns[col_name], col_prop, val)
obj._auto_init(cr, ctx)
+ openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res
class ir_model_constraint(Model):
diff --git a/openerp/addons/base/ir/ir_model_view.xml b/openerp/addons/base/ir/ir_model_view.xml
index 0f13837a65a..15052e6e5c5 100644
--- a/openerp/addons/base/ir/ir_model_view.xml
+++ b/openerp/addons/base/ir/ir_model_view.xml
@@ -151,7 +151,7 @@
'readonly': [('ttype','not in', ['many2one','one2many','many2many'])]}"/>
-
+
diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py
index 0b8ff4ee66f..09f73662070 100644
--- a/openerp/addons/base/ir/ir_ui_view.py
+++ b/openerp/addons/base/ir/ir_ui_view.py
@@ -83,7 +83,8 @@ class view(osv.osv):
}
_defaults = {
'arch': '\n\n\t\n',
- 'priority': 16
+ 'priority': 16,
+ 'type': 'tree',
}
_order = "priority,name"
diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py
index 7e370e86f0e..e0c38c969ec 100644
--- a/openerp/addons/base/module/module.py
+++ b/openerp/addons/base/module/module.py
@@ -411,7 +411,6 @@ class module(osv.osv):
if to_install_ids:
self.button_install(cr, uid, to_install_ids, context=context)
- openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return dict(ACTION_DICT, name=_('Install'))
def button_immediate_install(self, cr, uid, ids, context=None):
@@ -500,7 +499,6 @@ class module(osv.osv):
raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled"))
dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
- openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return dict(ACTION_DICT, name=_('Uninstall'))
def button_uninstall_cancel(self, cr, uid, ids, context=None):
diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py
index 21c9b854ca3..022c9c06da0 100644
--- a/openerp/addons/base/res/res_company.py
+++ b/openerp/addons/base/res/res_company.py
@@ -305,7 +305,7 @@ class res_company(osv.osv):
-
+
@@ -344,8 +344,8 @@ class res_company(osv.osv):
"""
- _header_a4 = _header_main % ('23.0cm', '27.6cm', '27.7cm', '27.7cm', '27.8cm', '27.3cm', '25.3cm', '25.0cm', '25.0cm', '24.6cm', '24.6cm', '24.5cm', '24.5cm')
- _header_letter = _header_main % ('21.3cm', '25.9cm', '26.0cm', '26.0cm', '26.1cm', '25.6cm', '23.6cm', '23.3cm', '23.3cm', '22.9cm', '22.9cm', '22.8cm', '22.8cm')
+ _header_a4 = _header_main % ('21.7cm', '27.7cm', '27.7cm', '27.7cm', '27.8cm', '27.3cm', '25.3cm', '25.0cm', '25.0cm', '24.6cm', '24.6cm', '24.5cm', '24.5cm')
+ _header_letter = _header_main % ('20cm', '26.0cm', '26.0cm', '26.0cm', '26.1cm', '25.6cm', '23.6cm', '23.3cm', '23.3cm', '22.9cm', '22.9cm', '22.8cm', '22.8cm')
def onchange_paper_format(self, cr, uid, ids, paper_format, context=None):
if paper_format == 'us_letter':
diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py
index 9e5eec6cfed..98dd6ed00d8 100644
--- a/openerp/addons/base/res/res_currency.py
+++ b/openerp/addons/base/res/res_currency.py
@@ -49,7 +49,7 @@ class res_currency(osv.osv):
id, rate = cr.fetchall()[0]
res[id] = rate
else:
- res[id] = 0
+ raise osv.except_osv(_('Error!'),_("No currency rate associated for currency %d for the given period" % (id)))
return res
_name = "res.currency"
_description = "Currency"
diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py
index aef2af80d5f..31e2d44b306 100644
--- a/openerp/addons/base/res/res_partner.py
+++ b/openerp/addons/base/res/res_partner.py
@@ -375,16 +375,30 @@ class res_partner(osv.osv, format_address):
def create(self, cr, uid, vals, context=None):
if context is None:
- context={}
+ context = {}
# Update parent and siblings records
- if vals.get('parent_id') and vals.get('use_parent_address'):
- domain_siblings = [('parent_id', '=', vals['parent_id']), ('use_parent_address', '=', True)]
- update_ids = [vals['parent_id']] + self.search(cr, uid, domain_siblings, context=context)
- self.update_address(cr, uid, update_ids, vals, context)
- return super(res_partner,self).create(cr, uid, vals, context=context)
+ if vals.get('parent_id'):
+ if 'use_parent_address' in vals:
+ use_parent_address = vals['use_parent_address']
+ else:
+ use_parent_address = self.default_get(cr, uid, ['use_parent_address'], context=context)['use_parent_address']
+
+ if use_parent_address:
+ domain_siblings = [('parent_id', '=', vals['parent_id']), ('use_parent_address', '=', True)]
+ update_ids = [vals['parent_id']] + self.search(cr, uid, domain_siblings, context=context)
+ self.update_address(cr, uid, update_ids, vals, context)
+
+ # add missing address keys
+ onchange_values = self.onchange_address(cr, uid, [], use_parent_address,
+ vals['parent_id'], context=context).get('value') or {}
+ vals.update(dict((key, value)
+ for key, value in onchange_values.iteritems()
+ if key in ADDRESS_FIELDS and key not in vals))
+
+ return super(res_partner, self).create(cr, uid, vals, context=context)
def update_address(self, cr, uid, ids, vals, context=None):
- addr_vals = dict((key, vals[key]) for key in POSTAL_ADDRESS_FIELDS if vals.get(key))
+ addr_vals = dict((key, vals[key]) for key in POSTAL_ADDRESS_FIELDS if key in vals)
if addr_vals:
return super(res_partner, self).write(cr, uid, ids, addr_vals, context)
@@ -411,10 +425,10 @@ class res_partner(osv.osv, format_address):
""" Supported syntax:
- 'Raoul ': will find name and email address
- otherwise: default, everything is set as the name """
- match = re.search(r'([^\s,<@]+@[^>\s,]+)', text)
- if match:
- email = match.group(1)
- name = text[:text.index(email)].replace('"','').replace('<','').strip()
+ emails = tools.email_split(text)
+ if emails:
+ email = emails[0]
+ name = text[:text.index(email)].replace('"', '').replace('<', '').strip()
else:
name, email = text, ''
return name, email
@@ -457,8 +471,7 @@ class res_partner(osv.osv, format_address):
OR partner.name || ' (' || COALESCE(company.name,'') || ')'
''' + operator + ' %(name)s ' + limit_str, query_args)
ids = map(lambda x: x[0], cr.fetchall())
- if args:
- ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context)
+ ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context)
if ids:
return self.name_get(cr, uid, ids, context)
return super(res_partner,self).name_search(cr, uid, name, args, operator=operator, context=context, limit=limit)
diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml
index c920aba23ae..9d5f55052d0 100644
--- a/openerp/addons/base/res/res_partner_view.xml
+++ b/openerp/addons/base/res/res_partner_view.xml
@@ -305,15 +305,15 @@
filter_domain="['|','|',('name','ilike',self),('parent_id','ilike',self),('ref','=',self)]"/>
-
-
+
+
-
+
+
-
-
+
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index 063996bec72..b0040d7c879 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL ().
-# Copyright (C) 2010-2012 OpenERP s.a. ().
+# Copyright (C) 2010-2013 OpenERP s.a. ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -172,6 +172,10 @@ class res_users(osv.osv):
}
}
+ def onchange_state(self, cr, uid, ids, state_id, context=None):
+ partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
+ return self.pool.get('res.partner').onchange_state(cr, uid, partner_ids, state_id, context=context)
+
def onchange_type(self, cr, uid, ids, is_company, context=None):
""" Wrapper on the user.partner onchange_type, because some calls to the
partner form view applied to the user may trigger the
@@ -426,7 +430,9 @@ class res_users(osv.osv):
cr = self.pool.db.cursor()
try:
base = user_agent_env['base_location']
- self.pool['ir.config_parameter'].set_param(cr, uid, 'web.base.url', base)
+ ICP = self.pool['ir.config_parameter']
+ if not ICP.get_param(cr, uid, 'web.base.url.freeze'):
+ ICP.set_param(cr, uid, 'web.base.url', base)
cr.commit()
except Exception:
_logger.exception("Failed to update web.base.url configuration parameter")
diff --git a/openerp/addons/base/static/src/js/apps.js b/openerp/addons/base/static/src/js/apps.js
index a55c89a0483..4ac01006256 100644
--- a/openerp/addons/base/static/src/js/apps.js
+++ b/openerp/addons/base/static/src/js/apps.js
@@ -56,7 +56,8 @@ openerp.base = function(instance) {
});
};
- i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif', client.origin);
+ var ts = new Date().getTime();
+ i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif?%s', client.origin, ts);
return d.promise();
};
if (instance.base.apps_client) {
@@ -96,7 +97,7 @@ openerp.base = function(instance) {
client.replace(self.$el).
done(function() {
client.$el.removeClass('openerp');
- client.do_action(self.remote_action_id);
+ client.do_action(self.remote_action_id, {hide_breadcrumb: true});
});
}).
fail(function(client) {
diff --git a/openerp/addons/base/tests/test_expression.py b/openerp/addons/base/tests/test_expression.py
index c43dda85e9b..49bc7d57ca1 100644
--- a/openerp/addons/base/tests/test_expression.py
+++ b/openerp/addons/base/tests/test_expression.py
@@ -114,6 +114,12 @@ class test_expression(common.TransactionCase):
# Test2: inheritance + relational fields
user_ids = users_obj.search(cr, uid, [('child_ids.name', 'like', 'test_B')])
self.assertEqual(set(user_ids), set([b1]), 'searching through inheritance failed')
+
+ # Special =? operator mean "is equal if right is set, otherwise always True"
+ user_ids = users_obj.search(cr, uid, [('name', 'like', 'test'), ('parent_id', '=?', False)])
+ self.assertEqual(set(user_ids), set([a, b1, b2]), '(x =? False) failed')
+ user_ids = users_obj.search(cr, uid, [('name', 'like', 'test'), ('parent_id', '=?', b1_user.partner_id.id)])
+ self.assertEqual(set(user_ids), set([b2]), '(x =? id) failed')
def test_20_auto_join(self):
registry, cr, uid = self.registry, self.cr, self.uid
diff --git a/openerp/cli/server.py b/openerp/cli/server.py
index 9339e590b7c..638fb081cac 100644
--- a/openerp/cli/server.py
+++ b/openerp/cli/server.py
@@ -220,15 +220,7 @@ def quit_on_signals():
os.unlink(config['pidfile'])
sys.exit(0)
-def configure_babel_localedata_path():
- # Workaround: py2exe and babel.
- if hasattr(sys, 'frozen'):
- import babel
- babel.localedata._dirname = os.path.join(os.path.dirname(sys.executable), 'localedata')
-
def main(args):
- os.environ["TZ"] = "UTC"
-
check_root_user()
openerp.tools.config.parse_config(args)
@@ -246,8 +238,6 @@ def main(args):
config = openerp.tools.config
- configure_babel_localedata_path()
-
setup_signal_handlers(signal_handler)
if config["test_file"]:
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index 7a1b2973696..a49dd891501 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -34,6 +34,7 @@ import openerp
import openerp.modules.db
import openerp.modules.graph
import openerp.modules.migration
+import openerp.modules.registry
import openerp.osv as osv
import openerp.tools as tools
from openerp import SUPERUSER_ID
@@ -131,7 +132,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
loaded_modules = []
registry = openerp.registry(cr.dbname)
migrations = openerp.modules.migration.MigrationManager(cr, graph)
- _logger.debug('loading %d packages...', len(graph))
+ _logger.info('loading %d modules...', len(graph))
# Query manual fields for all models at once and save them on the registry
# so the initialization code for each model does not have to do it
@@ -149,7 +150,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if skip_modules and module_name in skip_modules:
continue
- _logger.info('module %s: loading objects', package.name)
+ _logger.debug('module %s: loading objects', package.name)
migrations.migrate_module(package, 'pre')
load_openerp_module(package.name)
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index 89cb6143a59..48e39a60d33 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -190,6 +190,10 @@ class RegistryManager(object):
except KeyError:
return cls.new(db_name, force_demo, status,
update_module)
+ finally:
+ # set db tracker - cleaned up at the WSGI
+ # dispatching phase in openerp.service.wsgi_server.application
+ threading.current_thread().dbname = db_name
@classmethod
def new(cls, db_name, force_demo=False, status=None,
@@ -231,6 +235,9 @@ class RegistryManager(object):
registry.ready = True
+ if update_module:
+ # only in case of update, otherwise we'll have an infinite reload loop!
+ cls.signal_registry_change(db_name)
return registry
@classmethod
diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py
index 6989be2ccb3..cf2cbae3bad 100644
--- a/openerp/osv/expression.py
+++ b/openerp/osv/expression.py
@@ -198,7 +198,7 @@ def normalize_domain(domain):
expected -= 1
else:
expected += op_arity.get(token, 0) - 1
- assert expected == 0
+ assert expected == 0, 'This domain is syntactically not correct: %s' % (domain)
return result
@@ -597,6 +597,15 @@ class ExtendedLeaf(object):
self.leaf = normalize_leaf(self.leaf)
return True
+def create_substitution_leaf(leaf, new_elements, new_model=None):
+ """ From a leaf, create a new leaf (based on the new_elements tuple
+ and new_model), that will have the same join context. Used to
+ insert equivalent leafs in the processing stack. """
+ if new_model is None:
+ new_model = leaf.model
+ new_join_context = [tuple(context) for context in leaf.join_context]
+ new_leaf = ExtendedLeaf(new_elements, new_model, join_context=new_join_context)
+ return new_leaf
class expression(object):
""" Parse a domain expression
@@ -714,16 +723,6 @@ class expression(object):
return ids + recursive_children(ids2, model, parent_field)
return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
- def create_substitution_leaf(leaf, new_elements, new_model=None):
- """ From a leaf, create a new leaf (based on the new_elements tuple
- and new_model), that will have the same join context. Used to
- insert equivalent leafs in the processing stack. """
- if new_model is None:
- new_model = leaf.model
- new_join_context = [tuple(context) for context in leaf.join_context]
- new_leaf = ExtendedLeaf(new_elements, new_model, join_context=new_join_context)
- return new_leaf
-
def pop():
""" Pop a leaf to process. """
return self.stack.pop()
@@ -1152,7 +1151,8 @@ class expression(object):
params = []
else:
# '=?' behaves like '=' in other cases
- query, params = self.__leaf_to_sql((left, '=', right), model)
+ query, params = self.__leaf_to_sql(
+ create_substitution_leaf(eleaf, (left, '=', right), model))
elif left == 'id':
query = '%s.id %s %%s' % (table_alias, operator)
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 69c7498c6b9..8529bd8bab6 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -1028,7 +1028,7 @@ class BaseModel(object):
'required': bool(field['required']),
'readonly': bool(field['readonly']),
'domain': eval(field['domain']) if field['domain'] else None,
- 'size': field['size'],
+ 'size': field['size'] or None,
'ondelete': field['on_delete'],
'translate': (field['translate']),
'manual': True,
@@ -4451,7 +4451,6 @@ class BaseModel(object):
upd1 += ",%s,(now() at time zone 'UTC'),%s,(now() at time zone 'UTC')"
upd2.extend((user, user))
cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
- self.check_access_rule(cr, user, [id_new], 'create', context=context)
upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
if self._parent_store and not context.get('defer_parent_store_computation'):
@@ -4504,6 +4503,7 @@ class BaseModel(object):
self.name_get(cr, user, [id_new], context=context)[0][1] + \
"' " + _("created.")
self.log(cr, user, id_new, message, True, context=context)
+ self.check_access_rule(cr, user, [id_new], 'create', context=context)
self.create_workflow(cr, user, [id_new], context=context)
return id_new
diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py
index 57699f4c807..7973ac56ec8 100644
--- a/openerp/report/render/rml2pdf/trml2pdf.py
+++ b/openerp/report/render/rml2pdf/trml2pdf.py
@@ -49,6 +49,19 @@ _logger = logging.getLogger(__name__)
encoding = 'utf-8'
+def select_fontname(fontname, default_fontname):
+ if fontname not in pdfmetrics.getRegisteredFontNames()\
+ or fontname not in pdfmetrics.standardFonts:
+ # let reportlab attempt to find it
+ try:
+ pdfmetrics.getFont(fontname)
+ except Exception:
+ _logger.warning('Could not locate font %s, substituting default: %s',
+ fontname, default_fontname)
+ fontname = default_fontname
+ return fontname
+
+
def _open_image(filename, path=None):
"""Attempt to open a binary file and return the descriptor
"""
@@ -159,7 +172,12 @@ class _rml_styles(object,):
for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
if node.get(attr):
data[attr] = color.get(node.get(attr))
- for attr in ['fontName', 'bulletFontName', 'bulletText']:
+ for attr in ['bulletFontName', 'fontName']:
+ if node.get(attr):
+ fontname= select_fontname(node.get(attr), None)
+ if fontname is not None:
+ data['fontName'] = fontname
+ for attr in ['bulletText']:
if node.get(attr):
data[attr] = node.get(attr)
for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
@@ -537,17 +555,7 @@ class _rml_canvas(object):
self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
def setFont(self, node):
- fontname = node.get('name')
- if fontname not in pdfmetrics.getRegisteredFontNames()\
- or fontname not in pdfmetrics.standardFonts:
- # let reportlab attempt to find it
- try:
- pdfmetrics.getFont(fontname)
- except Exception:
- _logger.debug('Could not locate font %s, substituting default: %s',
- fontname,
- self.canvas._fontname)
- fontname = self.canvas._fontname
+ fontname = select_fontname(node.get('name'), self.canvas._fontname)
return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
def render(self, node):
diff --git a/openerp/service/cron.py b/openerp/service/cron.py
index 3155ed4259b..fe57aa7186c 100644
--- a/openerp/service/cron.py
+++ b/openerp/service/cron.py
@@ -30,6 +30,7 @@ cron jobs, for all databases of a single OpenERP server instance.
import logging
import threading
import time
+from datetime import datetime
import openerp
@@ -56,6 +57,12 @@ def start_service():
threads it spawns are not marked daemon).
"""
+
+ # Force call to strptime just before starting the cron thread
+ # to prevent time.strptime AttributeError within the thread.
+ # See: http://bugs.python.org/issue7980
+ datetime.strptime('2012-01-01', '%Y-%m-%d')
+
for i in range(openerp.tools.config['max_cron_threads']):
def target():
cron_runner(i)
diff --git a/openerp/service/db.py b/openerp/service/db.py
index 07d9085ae5b..a480b65440a 100644
--- a/openerp/service/db.py
+++ b/openerp/service/db.py
@@ -197,18 +197,26 @@ def exp_drop(db_name):
return True
@contextlib.contextmanager
-def _set_pg_password_in_environment():
- """ On Win32, pg_dump (and pg_restore) require that
- :envvar:`PGPASSWORD` be set
+def _set_pg_password_in_environment(self):
+ """ On systems where pg_restore/pg_dump require an explicit
+ password (i.e. when not connecting via unix sockets, and most
+ importantly on Windows), it is necessary to pass the PG user
+ password in the environment or in a special .pgpass file.
This context management method handles setting
- :envvar:`PGPASSWORD` iif win32 and the envvar is not already
+ :envvar:`PGPASSWORD` if it is not already
set, and removing it afterwards.
+
+ See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
+
+ .. note:: This is not thread-safe, and should never be enabled for
+ SaaS (giving SaaS users the super-admin password is not a good idea
+ anyway)
"""
- if os.name != 'nt' or os.environ.get('PGPASSWORD'):
+ if os.environ.get('PGPASSWORD') or not tools.config['db_password']:
yield
else:
- os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
+ os.environ['PGPASSWORD'] = tools.config['db_password']
try:
yield
finally:
@@ -234,7 +242,7 @@ def exp_dump(db_name):
if not data or res:
_logger.error(
'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
- 'It should be provided as a -w command-line option, or as `db_password` in the '
+ 'You may need to create a .pgpass file for authentication, or specify `db_password` in the '
'server configuration file.\n %s', db_name, data)
raise Exception, "Couldn't dump database"
_logger.info('DUMP DB successful: %s', db_name)
diff --git a/openerp/service/model.py b/openerp/service/model.py
index 79f0ace4f2b..adaac5307ad 100644
--- a/openerp/service/model.py
+++ b/openerp/service/model.py
@@ -3,7 +3,9 @@
from functools import wraps
import logging
from psycopg2 import IntegrityError, errorcodes
+import random
import threading
+import time
import openerp
from openerp.tools.translate import translate
@@ -13,9 +15,16 @@ import security
_logger = logging.getLogger(__name__)
+PG_CONCURRENCY_ERRORS_TO_RETRY = (errorcodes.LOCK_NOT_AVAILABLE, errorcodes.SERIALIZATION_FAILURE, errorcodes.DEADLOCK_DETECTED)
+MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
+
def dispatch(method, params):
(db, uid, passwd ) = params[0:3]
+
+ # set uid tracker - cleaned up at the WSGI
+ # dispatching phase in openerp.service.wsgi_server.application
threading.current_thread().uid = uid
+
params = params[3:]
if method == 'obj_list':
raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
@@ -94,37 +103,50 @@ def check(f):
def _(src):
return tr(src, 'code')
- try:
- if openerp.registry(dbname)._init:
- raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
- return f(dbname, *args, **kwargs)
- except IntegrityError, inst:
- registry = openerp.registry(dbname)
- for key in registry._sql_error.keys():
- if key in inst[0]:
- raise openerp.osv.orm.except_orm(_('Constraint Error'), tr(registry._sql_error[key], 'sql_constraint') or inst[0])
- if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
- msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
- _logger.debug("IntegrityError", exc_info=True)
- try:
- errortxt = inst.pgerror.replace('«','"').replace('»','"')
- if '"public".' in errortxt:
- context = errortxt.split('"public".')[1]
- model_name = table = context.split('"')[1]
- else:
- last_quote_end = errortxt.rfind('"')
- last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
- model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
- model = table.replace("_",".")
- model_obj = registry.get(model)
- if model_obj:
- model_name = model_obj._description or model_obj._name
- msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
- except Exception:
- pass
- raise openerp.osv.orm.except_orm(_('Integrity Error'), msg)
- else:
- raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0])
+ tries = 0
+ while True:
+ try:
+ if openerp.registry(dbname)._init:
+ raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
+ return f(dbname, *args, **kwargs)
+ except OperationalError, e:
+ # Automatically retry the typical transaction serialization errors
+ if e.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
+ raise
+ if tries >= MAX_TRIES_ON_CONCURRENCY_FAILURE:
+ _logger.warning("%s, maximum number of tries reached" % errorcodes.lookup(e.pgcode))
+ raise
+ wait_time = random.uniform(0.0, 2 ** tries)
+ tries += 1
+ _logger.info("%s, retry %d/%d in %.04f sec..." % (errorcodes.lookup(e.pgcode), tries, MAX_TRIES_ON_CONCURRENCY_FAILURE, wait_time))
+ time.sleep(wait_time)
+ except IntegrityError, inst:
+ registry = openerp.registry(dbname)
+ for key in registry._sql_error.keys():
+ if key in inst[0]:
+ raise openerp.osv.orm.except_orm(_('Constraint Error'), tr(registry._sql_error[key], 'sql_constraint') or inst[0])
+ if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
+ msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
+ _logger.debug("IntegrityError", exc_info=True)
+ try:
+ errortxt = inst.pgerror.replace('«','"').replace('»','"')
+ if '"public".' in errortxt:
+ context = errortxt.split('"public".')[1]
+ model_name = table = context.split('"')[1]
+ else:
+ last_quote_end = errortxt.rfind('"')
+ last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
+ model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
+ model = table.replace("_",".")
+ model_obj = registry.get(model)
+ if model_obj:
+ model_name = model_obj._description or model_obj._name
+ msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
+ except Exception:
+ pass
+ raise openerp.osv.orm.except_orm(_('Integrity Error'), msg)
+ else:
+ raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0])
return wrapper
diff --git a/openerp/service/workers.py b/openerp/service/workers.py
index 56d4d17583d..68433472aaf 100644
--- a/openerp/service/workers.py
+++ b/openerp/service/workers.py
@@ -388,9 +388,19 @@ class WorkerBaseWSGIServer(werkzeug.serving.BaseWSGIServer):
class WorkerCron(Worker):
""" Cron workers """
+
+ def __init__(self, multi):
+ super(WorkerCron, self).__init__(multi)
+ # process_work() below process a single database per call.
+ # The variable db_index is keeping track of the next database to
+ # process.
+ self.db_index = 0
+
def sleep(self):
- interval = 60 + self.pid % 10 # chorus effect
- time.sleep(interval)
+ # Really sleep once all the databases have been processed.
+ if self.db_index == 0:
+ interval = 60 + self.pid % 10 # chorus effect
+ time.sleep(interval)
def process_work(self):
rpc_request = logging.getLogger('openerp.netsvc.rpc.request')
@@ -400,7 +410,9 @@ class WorkerCron(Worker):
db_names = config['db_name'].split(',')
else:
db_names = openerp.service.db.exp_list(True)
- for db_name in db_names:
+ if len(db_names):
+ self.db_index = (self.db_index + 1) % len(db_names)
+ db_name = db_names[self.db_index]
if rpc_request_flag:
start_time = time.time()
start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
@@ -419,8 +431,14 @@ class WorkerCron(Worker):
end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
logline = '%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (db_name, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
_logger.debug("WorkerCron (%s) %s", self.pid, logline)
- # TODO Each job should be considered as one request instead of each run
- self.request_count += 1
+
+ self.request_count += 1
+ if self.request_count >= self.request_max and self.request_max < len(db_names):
+ _logger.error("There are more dabatases to process than allowed "
+ "by the `limit_request` configuration variable: %s more.",
+ len(db_names) - self.request_max)
+ else:
+ self.db_index = 0
def start(self):
Worker.start(self)
diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py
index 412a9f85263..df201825d31 100644
--- a/openerp/service/wsgi_server.py
+++ b/openerp/service/wsgi_server.py
@@ -390,6 +390,16 @@ def register_rpc_endpoint(endpoint, handler):
def application_unproxied(environ, start_response):
""" WSGI entry point."""
+ # cleanup db/uid trackers - they're set at HTTP dispatch in
+ # web.session.OpenERPSession.send() and at RPC dispatch in
+ # openerp.service.web_services.objects_proxy.dispatch().
+ # /!\ The cleanup cannot be done at the end of this `application`
+ # method because werkzeug still produces relevant logging afterwards
+ if hasattr(threading.current_thread(), 'uid'):
+ del threading.current_thread().uid
+ if hasattr(threading.current_thread(), 'dbname'):
+ del threading.current_thread().dbname
+
openerp.service.start_internal()
# Try all handlers until one returns some result (i.e. not None).
@@ -401,7 +411,6 @@ def application_unproxied(environ, start_response):
continue
return result
-
# We never returned from the loop.
response = 'No handler found.\n'
start_response('404 Not Found', [('Content-Type', 'text/plain'), ('Content-Length', str(len(response)))])
diff --git a/openerp/sql_db.py b/openerp/sql_db.py
index c27784433c0..7a58a5fc515 100644
--- a/openerp/sql_db.py
+++ b/openerp/sql_db.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL ().
-# Copyright (C) 2010-2011 OpenERP s.a. ().
+# Copyright (C) 2010-2013 OpenERP s.a. ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -36,7 +36,6 @@ import psycopg2.extensions
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ
from psycopg2.pool import PoolError
from psycopg2.psycopg1 import cursor as psycopg1cursor
-from threading import currentThread
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
@@ -393,7 +392,7 @@ class ConnectionPool(object):
def borrow(self, dsn):
self._debug('Borrow connection to %r', dsn)
- # free leaked connections
+ # free dead and leaked connections
for i, (cnx, _) in tools.reverse_enumerate(self._connections):
if cnx.closed:
self._connections.pop(i)
@@ -407,6 +406,14 @@ class ConnectionPool(object):
for i, (cnx, used) in enumerate(self._connections):
if not used and dsn_are_equals(cnx.dsn, dsn):
+ try:
+ cnx.reset()
+ except psycopg2.OperationalError:
+ self._debug('Cannot reset connection at index %d: %r', i, cnx.dsn)
+ # psycopg2 2.4.4 and earlier do not allow closing a closed connection
+ if not cnx.closed:
+ cnx.close()
+ continue
self._connections.pop(i)
self._connections.append((cnx, True))
self._debug('Existing connection found at index %d', i)
@@ -507,7 +514,6 @@ def db_connect(db_name):
global _Pool
if _Pool is None:
_Pool = ConnectionPool(int(tools.config['db_maxconn']))
- currentThread().dbname = db_name
return Connection(_Pool, db_name)
def close_db(db_name):
@@ -515,9 +521,6 @@ def close_db(db_name):
global _Pool
if _Pool:
_Pool.close_all(dsn(db_name))
- ct = currentThread()
- if hasattr(ct, 'dbname'):
- delattr(ct, 'dbname')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/tools/image.py b/openerp/tools/image.py
index d6a9e3ba24c..5e5055f252d 100644
--- a/openerp/tools/image.py
+++ b/openerp/tools/image.py
@@ -83,6 +83,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
if image.size != size:
# If you need faster thumbnails you may use use Image.NEAREST
image = ImageOps.fit(image, size, Image.ANTIALIAS)
+ if image.mode not in ["1", "L", "P", "RGB", "RGBA"]:
+ image = image.convert("RGB")
background_stream = StringIO.StringIO()
image.save(background_stream, filetype)
diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py
index 1fe22094831..5970ce47040 100644
--- a/openerp/tools/mail.py
+++ b/openerp/tools/mail.py
@@ -50,7 +50,7 @@ def html_sanitize(src):
src = ustr(src, errors='replace')
# html encode email tags
- part = re.compile(r"(<[^<>]+@[^<>]+>)", re.IGNORECASE | re.DOTALL)
+ part = re.compile(r"(<(([^a<>]|a[^<>\s])[^<>]*)@[^<>]+>)", re.IGNORECASE | re.DOTALL)
src = part.sub(lambda m: cgi.escape(m.group(1)), src)
# some corner cases make the parser crash (such as in test_mail)
@@ -185,6 +185,8 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
url_index.append(url)
html = ustr(etree.tostring(tree, encoding=encoding))
+ # \r char is converted into
, must remove it
+ html = html.replace('
', '')
html = html.replace('', '*').replace('', '*')
html = html.replace('', '*').replace('', '*')
diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py
index bfc0ce20d6e..1b476fb9b2a 100644
--- a/openerp/tools/misc.py
+++ b/openerp/tools/misc.py
@@ -138,6 +138,7 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False):
# Is it below 'addons_path' or 'root_path'?
name = os.path.normcase(os.path.normpath(name))
for root in adps + [rtp]:
+ root = os.path.normcase(os.path.normpath(root)) + os.sep
if name.startswith(root):
base = root.rstrip(os.sep)
name = name[len(base) + 1:]
diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py
index 207d2f2992c..df6350465b7 100644
--- a/openerp/tools/translate.py
+++ b/openerp/tools/translate.py
@@ -550,6 +550,8 @@ def trans_parse_view(de):
res.append(de.get('sum').encode("utf8"))
if de.get("confirm"):
res.append(de.get('confirm').encode("utf8"))
+ if de.get("placeholder"):
+ res.append(de.get('placeholder').encode("utf8"))
for n in de:
res.extend(trans_parse_view(n))
return res
diff --git a/openerpcommand/cron.py b/openerpcommand/cron.py
index 16b983dbc50..abfe1272b17 100644
--- a/openerpcommand/cron.py
+++ b/openerpcommand/cron.py
@@ -25,7 +25,6 @@ def run(args):
openerp.cli.server.check_root_user()
openerp.netsvc.init_logger()
#openerp.cli.server.report_configuration()
- openerp.cli.server.configure_babel_localedata_path()
openerp.cli.server.setup_signal_handlers(openerp.cli.server.signal_handler)
import openerp.addons.base
if args.database:
diff --git a/openerpcommand/web.py b/openerpcommand/web.py
index 728318d1d9f..5875978ad32 100644
--- a/openerpcommand/web.py
+++ b/openerpcommand/web.py
@@ -49,7 +49,6 @@ def run(args):
openerp.cli.server.check_root_user()
openerp.netsvc.init_logger()
#openerp.cli.server.report_configuration()
- openerp.cli.server.configure_babel_localedata_path()
target = openerp.service.wsgi_server.serve
if not args.gevent:
diff --git a/setup.nsi b/setup.nsi
index ab58915e4e9..653a8798bb2 100644
--- a/setup.nsi
+++ b/setup.nsi
@@ -291,9 +291,10 @@ Function .onInit
!insertmacro MUI_LANGDLL_DISPLAY
ClearErrors
- EnumRegKey $0 HKLM "SOFTWARE\PostgreSQL" 0
+ EnumRegKey $0 HKLM "SOFTWARE\PostgreSQL\Installations" 0
IfErrors DoInstallPostgreSQL 0
- StrCpy $HasPostgreSQL 1
+ StrCmp $0 "" DoInstallPostgreSQL
+ StrCpy $HasPostgreSQL 1
DoInstallPostgreSQL:
FunctionEnd
diff --git a/setup.py b/setup.py
index 7c57024d6d3..00bfa571d2c 100755
--- a/setup.py
+++ b/setup.py
@@ -35,7 +35,15 @@ def data():
r["Microsoft.VC90.CRT"] = glob.glob('C:\Microsoft.VC90.CRT\*.*')
import babel
- r["localedata"] = glob.glob(os.path.join(os.path.dirname(babel.__file__), "localedata", '*'))
+ # Add data, but also some .py files py2exe won't include automatically.
+ # TODO This should probably go under `packages`, instead of `data`,
+ # but this will work fine (especially since we don't use the ZIP file
+ # approach).
+ r["babel/localedata"] = glob.glob(os.path.join(os.path.dirname(babel.__file__), "localedata", '*'))
+ others = ['global.dat', 'numbers.py', 'support.py']
+ r["babel"] = map(lambda f: os.path.join(os.path.dirname(babel.__file__), f), others)
+ others = ['frontend.py', 'mofile.py']
+ r["babel/messages"] = map(lambda f: os.path.join(os.path.dirname(babel.__file__), "messages", f), others)
import pytz
tzdir = os.path.dirname(pytz.__file__)
@@ -66,7 +74,7 @@ def py2exe_options():
'options' : {
"py2exe": {
"skip_archive": 1,
- "optimize": 2,
+ "optimize": 0, # keep the assert running, because the integrated tests rely on them.
"dist_dir": 'dist',
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
"excludes" : ["Tkconstants","Tkinter","tcl"],
@@ -118,7 +126,7 @@ setuptools.setup(
'mock',
'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
'psutil', # windows binary code.google.com/p/psutil/downloads/list
- 'psycopg2',
+ 'psycopg2 >= 2.2',
'pydot',
'pyparsing < 2',
'python-dateutil < 2',