diff --git a/trytond/convert.py b/trytond/convert.py index 62452caba7..7953e79ad1 100644 --- a/trytond/convert.py +++ b/trytond/convert.py @@ -983,8 +983,7 @@ def post_import(pool, module, to_delete): cursor.commit() except Exception: cursor.rollback() - tb_s = reduce(lambda x, y: x + y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) logging.getLogger("convert").error( 'Could not delete id: %d of model %s\n' 'There should be some relation ' @@ -992,7 +991,7 @@ def post_import(pool, module, to_delete): 'You should manually fix this ' 'and restart --update=module\n' 'Exception: %s' % - (db_id, model, tb_s.decode('utf-8', 'ignore'))) + (db_id, model, tb_s)) transition_obj = pool.get('workflow.transition') for mdata_id, db_id in transition_delete: diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py index 9e44af860a..d7e9aae545 100644 --- a/trytond/ir/cron.py +++ b/trytond/ir/cron.py @@ -104,8 +104,7 @@ class Cron(ModelSQL, ModelView): return _INTERVALTYPES[cron.interval_type](cron.interval_number) def _get_request_values(self, cron): - tb_s = reduce(lambda x, y: x + y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) tb_s = tb_s.decode('utf-8', 'ignore') name = self.raise_user_error('request_title', raise_exception=False) diff --git a/trytond/ir/session.py b/trytond/ir/session.py index 1b168d0505..c9e21b5b50 100644 --- a/trytond/ir/session.py +++ b/trytond/ir/session.py @@ -45,7 +45,14 @@ class Session(ModelSQL): find = False for session in sessions: timestamp = session.write_date or session.create_date - if abs((timestamp - now).total_seconds()) < timeout: + delta = timestamp - now + # total_seconds new in version 2.7 + if hasattr(delta, 'total_seconds'): + delta = delta.total_seconds() + else: + delta = (delta.microseconds + (delta.seconds + delta.days * 24 + * 3600) * 10**6) / 10**6 + if abs(delta) < timeout: if session.key == key: find = True else: diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index a8cbea30ab..fd6da2bd14 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -1126,8 +1126,10 @@ class TranslationExport(Wizard): return 'result' def default_result(self, session, fields): + file_ = session.result.file + session.result.file = False # No need to store it in session return { - 'file': session.result.file, + 'file': file_, } TranslationExport() diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 7bf882bda6..beaec4965a 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -857,8 +857,7 @@ class ModelStorage(Model): logger.error(exp) # XXX should raise Exception Transaction().cursor.rollback() - tb_s = reduce(lambda x, y: x + y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) warning = '%s\n%s' % (tb_s, warning) return (-1, res, exp, warning) done += 1 diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py index a78b0ba2c6..b1e09d6609 100644 --- a/trytond/protocols/dispatcher.py +++ b/trytond/protocols/dispatcher.py @@ -165,13 +165,12 @@ def dispatch(host, port, protocol, database_name, user, session, object_type, if CONFIG['verbose'] and not isinstance(exception, ( NotLogged, ConcurrencyException, UserError, UserWarning)): - tb_s = reduce(lambda x, y: x + y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) logger = logging.getLogger('dispatcher') logger.error('Exception calling method %s on ' \ '%s %s from %s@%s:%d/%s:\n' % \ (method, object_type, object_name, user, host, port, - database_name) + tb_s.decode('utf-8', 'ignore')) + database_name) + tb_s) transaction.cursor.rollback() raise if not (object_name == 'res.request' and method == 'request_get'): @@ -240,8 +239,7 @@ def create(database_name, password, lang, admin_password): res = True except Exception: logger.error('CREATE DB: %s failed' % (database_name,)) - tb_s = reduce(lambda x, y: x+y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) logger.error('Exception in call: \n' + tb_s) raise else: @@ -263,8 +261,7 @@ def drop(database_name, password): cursor.commit() except Exception: logger.error('DROP DB: %s failed' % (database_name,)) - tb_s = reduce(lambda x, y: x+y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) logger.error('Exception in call: \n' + tb_s) raise else: diff --git a/trytond/protocols/jsonrpc.py b/trytond/protocols/jsonrpc.py index 3516d227da..71d3efd6df 100644 --- a/trytond/protocols/jsonrpc.py +++ b/trytond/protocols/jsonrpc.py @@ -26,6 +26,8 @@ try: except ImportError: import json import base64 +import encodings + def object_hook(dct): if '__class__' in dct: @@ -111,13 +113,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): ConcurrencyException), exception: response['error'] = exception.args except Exception: - tb_s = '' - for line in traceback.format_exception(*sys.exc_info()): - try: - line = line.encode('utf-8', 'ignore') - except Exception: - continue - tb_s += line + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) for path in sys.path: tb_s = tb_s.replace(path, '') if CONFIG['debug_mode']: @@ -166,6 +162,18 @@ class SimpleJSONRPCRequestHandler(GZipRequestHandlerMixin, SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.send_header(self, keyword, value) + def do_GET(self): + if self.is_tryton_url(self.path): + self.send_tryton_url(self.path) + return + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + def do_HEAD(self): + if self.is_tryton_url(self.path): + self.send_tryton_url(self.path) + return + SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self) + def translate_path(self, path): """Translate a /-separated PATH to the local filename syntax. @@ -188,6 +196,21 @@ class SimpleJSONRPCRequestHandler(GZipRequestHandlerMixin, path = os.path.join(path, word) return path + def is_tryton_url(self, path): + words = path.split('/') + try: + return words[2] in ('model', 'wizard', 'report') + except IndexError: + return False + + def send_tryton_url(self, path): + self.send_response(301) + hostname = CONFIG['hostname'] or unicode(socket.getfqdn(), 'utf8') + hostname = '.'.join(encodings.idna.ToASCII(part) for part in + hostname.split('.')) + self.send_header('Location', 'tryton://%s%s' % (hostname, path)) + self.end_headers() + class SecureJSONRPCRequestHandler(SimpleJSONRPCRequestHandler): @@ -245,6 +268,8 @@ class SimpleThreadedJSONRPCServer(SocketServer.ThreadingMixIn, def server_bind(self): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setsockopt(socket.SOL_SOCKET, + socket.SO_KEEPALIVE, 1) SimpleJSONRPCServer.server_bind(self) diff --git a/trytond/protocols/webdav.py b/trytond/protocols/webdav.py index ad941550c4..54ee809706 100644 --- a/trytond/protocols/webdav.py +++ b/trytond/protocols/webdav.py @@ -83,6 +83,8 @@ class BaseThreadedHTTPServer(SocketServer.ThreadingMixIn, def server_bind(self): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setsockopt(socket.SOL_SOCKET, + socket.SO_KEEPALIVE, 1) BaseHTTPServer.HTTPServer.server_bind(self) @@ -137,10 +139,9 @@ class TrytonDAVInterface(iface.dav_interface): if CONFIG['verbose'] and not isinstance(exception, ( NotLogged, ConcurrencyException, UserError, UserWarning, DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden)): - tb_s = reduce(lambda x, y: x + y, - traceback.format_exception(*sys.exc_info())) + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) logger = logging.getLogger('webdav') - logger.error('Exception:\n' + tb_s.decode('utf-8', 'ignore')) + logger.error('Exception:\n' + tb_s) @staticmethod def get_dburi(uri): diff --git a/trytond/protocols/xmlrpc.py b/trytond/protocols/xmlrpc.py index f3e913ce70..65db29ab8e 100644 --- a/trytond/protocols/xmlrpc.py +++ b/trytond/protocols/xmlrpc.py @@ -113,13 +113,7 @@ class GenericXMLRPCRequestHandler: raise xmlrpclib.Fault(exception.code, '\n'.join((error,) + description)) except Exception: - tb_s = '' - for line in traceback.format_exception(*sys.exc_info()): - try: - line = line.encode('utf-8', 'ignore') - except Exception: - continue - tb_s += line + tb_s = ''.join(traceback.format_exception(*sys.exc_info())) for path in sys.path: tb_s = tb_s.replace(path, '') if CONFIG['debug_mode']: @@ -177,6 +171,8 @@ class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, def server_bind(self): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setsockopt(socket.SOL_SOCKET, + socket.SO_KEEPALIVE, 1) SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self) diff --git a/trytond/wizard/wizard.py b/trytond/wizard/wizard.py index 97cb921275..df8efb9da0 100644 --- a/trytond/wizard/wizard.py +++ b/trytond/wizard/wizard.py @@ -152,6 +152,7 @@ class _SessionRecord(object): self._model = model self._data = data self.__cache = {} + self.dirty = False def __getattr__(self, name): if name in self.__cache: @@ -182,6 +183,7 @@ class _SessionRecord(object): or name in self._model._inherit_fields)): self.__cache.pop(name, None) self._data[name] = value + self.dirty = True else: super(_SessionRecord, self).__setattr__(name, value) @@ -204,12 +206,18 @@ class Session(object): setattr(self, state_name, _SessionRecord(model, self.data[state_name])) + @property + def dirty(self): + return any(getattr(self, state_name).dirty + for state_name in self.data) + def save(self): "Save the session in database" session_obj = Pool().get('ir.session.wizard') - session_obj.write(self._session.id, { - 'data': json.dumps(self.data, cls=JSONEncoder), - }) + if self.dirty: + session_obj.write(self._session.id, { + 'data': json.dumps(self.data, cls=JSONEncoder), + }) class Wizard(WarningErrorMixin, URLMixin):