merge upstream
bzr revid: chs@openerp.com-20130411154519-xvepmjrm4ibqddx7
This commit is contained in:
Vendored
+2
-1
@@ -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
|
||||
|
||||
Vendored
+30
-39
@@ -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
|
||||
|
||||
Vendored
+3
-3
@@ -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
|
||||
|
||||
+11
-2
@@ -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
|
||||
|
||||
@@ -35,10 +46,8 @@ import osv
|
||||
import pooler
|
||||
import release
|
||||
import report
|
||||
import run_tests
|
||||
import service
|
||||
import sql_db
|
||||
import test
|
||||
import tools
|
||||
import workflow
|
||||
# backward compatilbility
|
||||
|
||||
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+298
-163
File diff suppressed because it is too large
Load Diff
+287
-154
File diff suppressed because it is too large
Load Diff
+340
-169
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+352
-194
File diff suppressed because it is too large
Load Diff
+797
-609
File diff suppressed because it is too large
Load Diff
+318
-161
File diff suppressed because it is too large
Load Diff
+2383
-185
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+302
-154
File diff suppressed because it is too large
Load Diff
+302
-161
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+296
-155
File diff suppressed because it is too large
Load Diff
+295
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+294
-156
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+1446
-238
File diff suppressed because it is too large
Load Diff
+292
-157
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+357
-196
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+486
-290
File diff suppressed because it is too large
Load Diff
+487
-346
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+296
-161
File diff suppressed because it is too large
Load Diff
+297
-156
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+293
-158
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+2075
-1653
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+325
-185
File diff suppressed because it is too large
Load Diff
+2143
-1517
File diff suppressed because it is too large
Load Diff
+631
-258
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+420
-191
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+303
-162
File diff suppressed because it is too large
Load Diff
+300
-159
File diff suppressed because it is too large
Load Diff
+3332
-535
File diff suppressed because it is too large
Load Diff
+518
-376
File diff suppressed because it is too large
Load Diff
+323
-167
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+299
-158
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+900
-718
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+296
-158
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+297
-156
File diff suppressed because it is too large
Load Diff
+289
-154
File diff suppressed because it is too large
Load Diff
+790
-585
File diff suppressed because it is too large
Load Diff
@@ -206,7 +206,7 @@ class act_window(osv.osv):
|
||||
def _search_view(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for act in self.browse(cr, uid, ids, context=context):
|
||||
field_get = self.pool.get(act.res_model).fields_view_get(cr, uid,
|
||||
field_get = self.pool[act.res_model].fields_view_get(cr, uid,
|
||||
act.search_view_id and act.search_view_id.id or False,
|
||||
'search', context=context)
|
||||
res[act.id] = str(field_get)
|
||||
@@ -608,7 +608,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']
|
||||
|
||||
@@ -791,7 +791,7 @@ Launch Manually Once: after having been launched manually, it sets automatically
|
||||
act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
|
||||
|
||||
res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
|
||||
if act_type<>'ir.actions.act_window':
|
||||
if act_type['type'] != 'ir.actions.act_window':
|
||||
return res
|
||||
res.setdefault('context','{}')
|
||||
res['nodestroy'] = True
|
||||
|
||||
@@ -21,12 +21,15 @@
|
||||
|
||||
import hashlib
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields,osv
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ir_attachment(osv.osv):
|
||||
"""Attachments are used to link binary files or url to any openerp document.
|
||||
|
||||
@@ -80,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
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import psycopg2
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
@@ -181,6 +182,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:
|
||||
@@ -237,6 +239,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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="model_id"/>
|
||||
<field name="is_default"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="domain"/>
|
||||
@@ -35,6 +36,7 @@
|
||||
<field name="name"/>
|
||||
<field name="model_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="is_default"/>
|
||||
<field name="domain" groups="base.group_no_one"/>
|
||||
<field name="context" groups="base.group_no_one"/>
|
||||
</tree>
|
||||
@@ -45,12 +47,14 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Filters">
|
||||
<field name="name" string="Filter Name"/>
|
||||
<filter string="Personal" domain="[('user_id','!=',False)]" help="Filters visible only for one user"/>
|
||||
<filter string="Shared" domain="[('user_id','=',False)]" help="Filters shared with all users"/>
|
||||
<filter string="User" domain="[('user_id','!=',False)]" name="user" help="Filters visible only for one user"/>
|
||||
<filter string="Shared" domain="[('user_id','=',False)]" name="shared" help="Filters shared with all users"/>
|
||||
<filter string="My filters" domain="[('user_id','=',uid)]" name="my_filters" help="Filters created by myself"/>
|
||||
<separator/>
|
||||
<filter icon="terp-personal" domain="[('user_id','in',(uid, False))]"
|
||||
name="my_filters"
|
||||
string="My Filters"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="User" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Model" domain="[]" context="{'group_by':'model_id'}"/>
|
||||
</group>
|
||||
<field name="model_id"/>
|
||||
<field name="user_id"/>
|
||||
</search>
|
||||
|
||||
@@ -25,6 +25,7 @@ import time
|
||||
import types
|
||||
|
||||
import openerp
|
||||
import openerp.modules.registry
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import netsvc, pooler, 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
|
||||
pooler.restart_pool(cr.dbname)
|
||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||
|
||||
return res
|
||||
|
||||
@@ -194,7 +197,7 @@ class ir_model(osv.osv):
|
||||
field_state='manual',
|
||||
select=vals.get('select_level', '0'))
|
||||
self.pool.get(vals['model'])._auto_init(cr, ctx)
|
||||
#pooler.restart_pool(cr.dbname)
|
||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||
return res
|
||||
|
||||
def instanciate(self, cr, user, model, context=None):
|
||||
@@ -260,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,
|
||||
}
|
||||
@@ -290,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):
|
||||
@@ -319,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):
|
||||
@@ -350,6 +355,7 @@ class ir_model_fields(osv.osv):
|
||||
select=vals.get('select_level', '0'),
|
||||
update_custom_fields=True)
|
||||
self.pool.get(vals['model'])._auto_init(cr, ctx)
|
||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||
|
||||
return res
|
||||
|
||||
@@ -466,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):
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
'readonly': [('ttype','not in', ['many2one','one2many','many2many'])]}"/>
|
||||
<field name="relation_field" attrs="{'required': [('ttype','=','one2many')], 'readonly': [('ttype','!=','one2many')]}"/>
|
||||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'required': [('ttype','in',['char','reference'])], 'readonly': [('ttype','not in',['char','reference'])]}"/>
|
||||
<field name="size" attrs="{'invisible': [('ttype','not in',['char','text','reference'])]}"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
<field name="serialization_field_id" attrs="{'readonly': [('state','=','base')]}" domain="[('ttype','=','serialized'), ('model_id', '=', model_id)]"/>
|
||||
<field name="on_delete" attrs="{'readonly': [('ttype','!=','many2one')]}"/>
|
||||
|
||||
@@ -83,7 +83,8 @@ class view(osv.osv):
|
||||
}
|
||||
_defaults = {
|
||||
'arch': '<?xml version="1.0"?>\n<tree string="My view">\n\t<field name="name"/>\n</tree>',
|
||||
'priority': 16
|
||||
'priority': 16,
|
||||
'type': 'tree',
|
||||
}
|
||||
_order = "priority,name"
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
<field name="model">res.partner.bank</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Bank Accounts">
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="acc_number"/>
|
||||
<field name="bank_name"/>
|
||||
|
||||
@@ -305,7 +305,7 @@ class res_company(osv.osv):
|
||||
<frame id="first" x1="1.3cm" y1="3.0cm" height="%s" width="19.0cm"/>
|
||||
<stylesheet>
|
||||
<paraStyle name="main_footer" fontName="DejaVu Sans" fontSize="8.0" alignment="CENTER"/>
|
||||
<paraStyle name="main_header" fontName="DejaVu Sans" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="main_header" fontName="DejaVu Sans" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
</stylesheet>
|
||||
<pageGraphics>
|
||||
<!-- You Logo - Change X,Y,Width and Height -->
|
||||
@@ -344,8 +344,8 @@ class res_company(osv.osv):
|
||||
</pageTemplate>
|
||||
</header>"""
|
||||
|
||||
_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':
|
||||
|
||||
@@ -573,7 +573,10 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
if action:
|
||||
return action
|
||||
|
||||
config = self.pool.get('res.config').next(cr, uid, [], context=context) or {}
|
||||
# After the uninstall/install calls, the self.pool is no longer valid.
|
||||
# So we reach into the RegistryManager directly.
|
||||
res_config = openerp.modules.registry.RegistryManager.get(cr.dbname)['res.config']
|
||||
config = res_config.next(cr, uid, [], context=context) or {}
|
||||
if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
return config
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
|
||||
name_search = location_name_search
|
||||
|
||||
def create(self, cursor, user, vals, context=None):
|
||||
if 'code' in vals:
|
||||
if vals.get('code'):
|
||||
vals['code'] = vals['code'].upper()
|
||||
return super(Country, self).create(cursor, user, vals,
|
||||
context=context)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -381,16 +381,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)
|
||||
|
||||
@@ -417,10 +431,10 @@ class res_partner(osv.osv, format_address):
|
||||
""" Supported syntax:
|
||||
- 'Raoul <raoul@grosbedon.fr>': 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
|
||||
@@ -463,8 +477,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)
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<field name="city">Wavre</field>
|
||||
<field name="zip">1300</field>
|
||||
<field name="country_id" ref="base.be"/>
|
||||
<field name="street">69 rue de Chimay</field>
|
||||
<field name="street">69 rue de Namur</field>
|
||||
<field name="email">info@agrolait.com</field>
|
||||
<field name="phone">+32 10 588 558</field>
|
||||
<field name="website">www.agrolait.com</field>
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
</group>
|
||||
|
||||
<notebook colspan="4">
|
||||
<page string="Contacts" attrs="{'invisible': [('is_company','=',False)]}">
|
||||
<page string="Contacts" attrs="{'invisible': [('is_company','=',False)]}" autofocus="autofocus">
|
||||
<field name="child_ids" context="{'default_parent_id': active_id}" mode="kanban">
|
||||
<kanban>
|
||||
<field name="color"/>
|
||||
@@ -304,18 +304,19 @@
|
||||
filter_domain="['|','|',('name','ilike',self),('parent_id','ilike',self),('ref','=',self)]"/>
|
||||
<filter help="My Partners" icon="terp-personal+" domain="[('user_id','=',uid)]"/>
|
||||
<separator/>
|
||||
<filter string="Persons" name="type_person" icon="terp-personal" domain="[('is_company','=',0)]"/>
|
||||
<filter string="Companies" name="type_company" icon="terp-partner" domain="[('is_company','=',1)]"/>
|
||||
<filter string="Persons" name="type_person" domain="[('is_company','=',0)]"/>
|
||||
<filter string="Companies" name="type_company" domain="[('is_company','=',1)]"/>
|
||||
<separator/>
|
||||
<filter string="Customers" name="customer" icon="terp-personal" domain="[('customer','=',1)]" help="Customer Partners"/>
|
||||
<filter string="Customers" name="customer" domain="[('customer','=',1)]" help="Customer Partners"/>
|
||||
<filter string="Suppliers" name="supplier" domain="[('supplier','=',1)]" help="Supplier Partners"/>
|
||||
<separator/>
|
||||
<filter string="Suppliers" name="supplier" icon="terp-personal" domain="[('supplier','=',1)]" help="Supplier Partners"/>
|
||||
<field name="category_id" string="Tag" filter_domain="[('category_id','ilike', self)]"/>
|
||||
<field name="user_id"/>
|
||||
<field name="parent_id" filter_domain="[('parent_id','child_of',[self])]"/>
|
||||
<field name="parent_id" domain="[('is_company','=',1)]" filter_domain="[('parent_id','child_of',[self])]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by' : 'user_id'}" />
|
||||
<filter string="Company" context="{'group_by': 'parent_id'}"/>
|
||||
<filter string="Country" context="{'group_by': 'country_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# 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 = pooler.get_db(db).cursor()
|
||||
try:
|
||||
base = user_agent_env['base_location']
|
||||
self.pool.get('ir.config_parameter').set_param(cr, uid, 'web.base.url', base)
|
||||
ICP = self.pool.get('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")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -218,15 +218,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)
|
||||
|
||||
@@ -236,8 +228,6 @@ def main(args):
|
||||
|
||||
config = openerp.tools.config
|
||||
|
||||
configure_babel_localedata_path()
|
||||
|
||||
setup_signal_handlers()
|
||||
|
||||
if config["test_file"]:
|
||||
|
||||
@@ -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.pooler as pooler
|
||||
import openerp.tools as tools
|
||||
@@ -138,7 +139,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
loaded_modules = []
|
||||
pool = pooler.get_pool(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
|
||||
@@ -156,7 +157,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)
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ def load_openerp_module(module_name):
|
||||
initialize_sys_path()
|
||||
try:
|
||||
mod_path = get_module_path(module_name)
|
||||
zip_mod_path = mod_path + '.zip'
|
||||
zip_mod_path = '' if not mod_path else mod_path + '.zip'
|
||||
if not os.path.isfile(zip_mod_path):
|
||||
__import__('openerp.addons.' + module_name)
|
||||
else:
|
||||
|
||||
@@ -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,
|
||||
@@ -216,6 +220,11 @@ class RegistryManager(object):
|
||||
del cls.registries[db_name]
|
||||
raise
|
||||
|
||||
# load_modules() above can replace the registry by calling
|
||||
# indirectly new() again (when modules have to be uninstalled).
|
||||
# Yeah, crazy.
|
||||
registry = cls.registries[db_name]
|
||||
|
||||
cr = registry.db.cursor()
|
||||
try:
|
||||
Registry.setup_multi_process_signaling(cr)
|
||||
@@ -227,6 +236,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
|
||||
|
||||
+7
-3
@@ -186,7 +186,13 @@ def init_logger():
|
||||
# Normal Handler on standard output
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
|
||||
if isinstance(handler, logging.StreamHandler) and os.isatty(handler.stream.fileno()):
|
||||
# Check that handler.stream has a fileno() method: when running OpenERP
|
||||
# behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log,
|
||||
# which has no fileno() method. (mod_wsgi.Log is what is being bound to
|
||||
# sys.stderr when the logging.StreamHandler is being constructed above.)
|
||||
if isinstance(handler, logging.StreamHandler) \
|
||||
and hasattr(handler.stream, 'fileno') \
|
||||
and os.isatty(handler.stream.fileno()):
|
||||
formatter = ColoredFormatter(format)
|
||||
else:
|
||||
formatter = DBFormatter(format)
|
||||
@@ -284,8 +290,6 @@ def dispatch_rpc(service_name, method, params):
|
||||
if rpc_request and rpc_response_flag:
|
||||
log(rpc_request,logging.DEBUG,'%s.%s'%(service_name,method), replace_request_password(params))
|
||||
|
||||
threading.current_thread().uid = None
|
||||
threading.current_thread().dbname = None
|
||||
result = ExportService.getService(service_name).dispatch(method, params)
|
||||
|
||||
if rpc_request_flag or rpc_response_flag:
|
||||
|
||||
+12
-12
@@ -199,7 +199,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
|
||||
|
||||
|
||||
@@ -598,6 +598,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
|
||||
@@ -715,16 +724,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()
|
||||
@@ -1153,7 +1152,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)
|
||||
|
||||
+20
-5
@@ -1029,7 +1029,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,
|
||||
@@ -1077,7 +1077,7 @@ class BaseModel(object):
|
||||
|
||||
# Validate rec_name
|
||||
if self._rec_name is not None:
|
||||
assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name)
|
||||
assert self._rec_name in self._all_columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name)
|
||||
else:
|
||||
self._rec_name = 'name'
|
||||
|
||||
@@ -1362,11 +1362,9 @@ class BaseModel(object):
|
||||
noupdate=noupdate, res_id=id, context=context))
|
||||
cr.execute('RELEASE SAVEPOINT model_load_save')
|
||||
except psycopg2.Warning, e:
|
||||
_logger.exception('Failed to import record %s', record)
|
||||
messages.append(dict(info, type='warning', message=str(e)))
|
||||
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
|
||||
except psycopg2.Error, e:
|
||||
_logger.exception('Failed to import record %s', record)
|
||||
messages.append(dict(
|
||||
info, type='error',
|
||||
**PGERROR_TO_OE[e.pgcode](self, fg, info, e)))
|
||||
@@ -4434,7 +4432,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'):
|
||||
@@ -4487,6 +4484,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._workflow_trigger(cr, user, [id_new], 'trg_create', context=context)
|
||||
return id_new
|
||||
|
||||
@@ -5327,11 +5325,28 @@ def convert_pgerror_23502(model, fields, info, e):
|
||||
'message': message,
|
||||
'field': field_name,
|
||||
}
|
||||
def convert_pgerror_23505(model, fields, info, e):
|
||||
m = re.match(r'^duplicate key (?P<field>\w+) violates unique constraint',
|
||||
str(e))
|
||||
field_name = m.group('field')
|
||||
if not m or field_name not in fields:
|
||||
return {'message': unicode(e)}
|
||||
message = _(u"The value for the field '%s' already exists.") % field_name
|
||||
field = fields.get(field_name)
|
||||
if field:
|
||||
message = _(u"%s This might be '%s' in the current model, or a field "
|
||||
u"of the same name in an o2m.") % (message, field['string'])
|
||||
return {
|
||||
'message': message,
|
||||
'field': field_name,
|
||||
}
|
||||
|
||||
PGERROR_TO_OE = collections.defaultdict(
|
||||
# shape of mapped converters
|
||||
lambda: (lambda model, fvg, info, pgerror: {'message': unicode(pgerror)}), {
|
||||
# not_null_violation
|
||||
'23502': convert_pgerror_23502,
|
||||
# unique constraint error
|
||||
'23505': convert_pgerror_23505,
|
||||
})
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
+59
-41
@@ -25,7 +25,7 @@ from functools import wraps
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from psycopg2 import IntegrityError, errorcodes
|
||||
from psycopg2 import IntegrityError, OperationalError, errorcodes
|
||||
|
||||
import orm
|
||||
import openerp
|
||||
@@ -36,8 +36,14 @@ from openerp.tools.translate import translate
|
||||
from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel
|
||||
import openerp.exceptions
|
||||
|
||||
import time
|
||||
import random
|
||||
|
||||
_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
|
||||
|
||||
# Deprecated.
|
||||
class except_osv(Exception):
|
||||
def __init__(self, name, value):
|
||||
@@ -117,45 +123,58 @@ class object_proxy(object):
|
||||
def _(src):
|
||||
return tr(src, 'code')
|
||||
|
||||
try:
|
||||
if pooler.get_pool(dbname)._init:
|
||||
raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
|
||||
return f(self, dbname, *args, **kwargs)
|
||||
except orm.except_orm, inst:
|
||||
raise except_osv(inst.name, inst.value)
|
||||
except except_osv:
|
||||
raise
|
||||
except IntegrityError, inst:
|
||||
osv_pool = pooler.get_pool(dbname)
|
||||
for key in osv_pool._sql_error.keys():
|
||||
if key in inst[0]:
|
||||
netsvc.abort_response(1, _('Constraint Error'), 'warning',
|
||||
tr(osv_pool._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 = osv_pool.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
|
||||
netsvc.abort_response(1, _('Integrity Error'), 'warning', msg)
|
||||
else:
|
||||
netsvc.abort_response(1, _('Integrity Error'), 'warning', inst[0])
|
||||
except Exception:
|
||||
_logger.exception("Uncaught exception")
|
||||
raise
|
||||
tries = 0
|
||||
while True:
|
||||
try:
|
||||
if pooler.get_pool(dbname)._init:
|
||||
raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
|
||||
return f(self, 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 orm.except_orm, inst:
|
||||
raise except_osv(inst.name, inst.value)
|
||||
except except_osv:
|
||||
raise
|
||||
except IntegrityError, inst:
|
||||
osv_pool = pooler.get_pool(dbname)
|
||||
for key in osv_pool._sql_error.keys():
|
||||
if key in inst[0]:
|
||||
netsvc.abort_response(1, _('Constraint Error'), 'warning',
|
||||
tr(osv_pool._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 = osv_pool.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
|
||||
netsvc.abort_response(1, _('Integrity Error'), 'warning', msg)
|
||||
else:
|
||||
netsvc.abort_response(1, _('Integrity Error'), 'warning', inst[0])
|
||||
except Exception:
|
||||
_logger.exception("Uncaught exception")
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -170,7 +189,6 @@ class object_proxy(object):
|
||||
|
||||
@check
|
||||
def execute(self, db, uid, obj, method, *args, **kw):
|
||||
threading.currentThread().dbname = db
|
||||
cr = pooler.get_db(db).cursor()
|
||||
try:
|
||||
try:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user