diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index d2e6339c06..251c90c6e9 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Add ButtonActionException and RPCReturnException * Rename ModelStorage.count into ModelStorage.estimated_count * Add border type to images * Lock table on transaction start diff --git a/trytond/doc/ref/exceptions.rst b/trytond/doc/ref/exceptions.rst index 38a3bf551b..98ccb0ce3a 100644 --- a/trytond/doc/ref/exceptions.rst +++ b/trytond/doc/ref/exceptions.rst @@ -46,6 +46,13 @@ Exceptions The exception raised when trying to execute a button without the rights. +.. exception:: ButtonActionException + + The exception raised to launch the ``action`` instead of executing the + button method. + + The ``value`` attribute is the action value returned. + .. exception:: ImportDataError The exception raises when importing data fails. diff --git a/trytond/doc/ref/rpc.rst b/trytond/doc/ref/rpc.rst index edb52132dd..91ab85235e 100644 --- a/trytond/doc/ref/rpc.rst +++ b/trytond/doc/ref/rpc.rst @@ -61,3 +61,14 @@ Instance methods are: .. method:: RCP.headers Return a dictionary of the headers. + +Exceptions +========== + +.. exception:: RPCReturnException + + The base class of exceptions to return the result of ``result`` method + instead of raising an exception. + + The :class:`~trytond.transaction.Transaction` is rollbacked and tasks are + cleared. diff --git a/trytond/trytond/model/exceptions.py b/trytond/trytond/model/exceptions.py index 1d47e7ee09..5d8627d464 100644 --- a/trytond/trytond/model/exceptions.py +++ b/trytond/trytond/model/exceptions.py @@ -7,12 +7,13 @@ from .modelstorage import ( ForbiddenCharValidationError, ImportDataError, RequiredValidationError, SelectionValidationError, SizeValidationError, TimeFormatValidationError, ValidationError) -from .modelview import AccessButtonError +from .modelview import AccessButtonError, ButtonActionException from .tree import RecursionError __all__ = [ AccessButtonError, AccessError, + ButtonActionException, DigitsValidationError, DomainValidationError, ForeignKeyError, diff --git a/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py index 8c0ea5eb80..d1b478c5ef 100644 --- a/trytond/trytond/model/modelview.py +++ b/trytond/trytond/model/modelview.py @@ -10,7 +10,7 @@ from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.pool import Pool from trytond.pyson import PYSONEncoder -from trytond.rpc import RPC +from trytond.rpc import RPC, RPCReturnException from trytond.tools import is_instance_method from trytond.transaction import Transaction @@ -25,6 +25,26 @@ class AccessButtonError(UserError): pass +class ButtonActionException(RPCReturnException): + "Exception to launch action instead of executing button." + + def __init__(self, action, value=None): + super().__init__() + pool = Pool() + ModelData = pool.get('ir.model.data') + Action = pool.get('ir.action') + + module, fs_id = action.split('.') + action_id = Action.get_action_id( + ModelData.get_id(module, fs_id)) + self.value = Action(action_id).get_action_value() + if value: + self.value.update(value) + + def result(self): + return self.value + + def on_change(func): @wraps(func) def wrapper(self, *args, **kwargs): diff --git a/trytond/trytond/protocols/dispatcher.py b/trytond/trytond/protocols/dispatcher.py index c431a83c11..f5b0a5e20a 100644 --- a/trytond/trytond/protocols/dispatcher.py +++ b/trytond/trytond/protocols/dispatcher.py @@ -12,6 +12,7 @@ from trytond.config import config, get_hostname from trytond.exceptions import ( ConcurrencyException, LoginException, RateLimitException, UserError, UserWarning) +from trytond.rpc import RPCReturnException from trytond.tools import is_instance_method from trytond.transaction import Transaction, TransactionError from trytond.worker import run_task @@ -221,6 +222,10 @@ def _dispatch(request, pool, *args, **kwargs): continue logger.exception(log_message, *log_args, duration()) raise + except RPCReturnException as e: + transaction.rollback() + transaction.tasks.clear() + result = e.result() except (ConcurrencyException, UserError, UserWarning, LoginException): logger.info( diff --git a/trytond/trytond/rpc.py b/trytond/trytond/rpc.py index 52ff27188a..fbd6d2d098 100644 --- a/trytond/trytond/rpc.py +++ b/trytond/trytond/rpc.py @@ -3,10 +3,9 @@ import copy import datetime as dt +from trytond.exceptions import TrytonException from trytond.transaction import Transaction -__all__ = ['RPC'] - class RPC(object): '''Define RPC behavior @@ -95,3 +94,10 @@ class RPCCache: return { 'X-Tryton-Cache': int(self.duration.total_seconds()), } + + +class RPCReturnException(TrytonException): + "Exception to return response instead of being raised" + + def result(self): + pass diff --git a/trytond/trytond/tests/test_modelview.py b/trytond/trytond/tests/test_modelview.py index 31faf2f3f8..edc27653fb 100644 --- a/trytond/trytond/tests/test_modelview.py +++ b/trytond/trytond/tests/test_modelview.py @@ -6,7 +6,8 @@ from unittest.mock import patch from lxml import etree -from trytond.model.exceptions import AccessButtonError, AccessError +from trytond.model.exceptions import ( + AccessButtonError, AccessError, ButtonActionException) from trytond.pool import Pool from trytond.pyson import Eval, PYSONDecoder, PYSONEncoder from trytond.tests.test_tryton import activate_module, with_transaction @@ -689,3 +690,27 @@ class ModelView(unittest.TestCase): self.assertIn('bar', writeable_fields) self.assertIn('baz', writeable_fields) self.assertIn('quux', writeable_fields) + + @with_transaction() + def test_button_action_exception(self): + "Test value of ButtonActionException" + exception = ButtonActionException('tests.test_modelview_button_action') + self.assertEqual(exception.value, { + **exception.value, + 'name': "Test Button Action", + 'url': 'http://www.example.com/', + }) + + @with_transaction() + def test_button_action_exception_value(self): + "Test value of ButtonActionException" + exception = ButtonActionException( + 'tests.test_modelview_button_action', + value={ + 'url': 'https://www.example.net', + }) + self.assertEqual(exception.value, { + **exception.value, + 'name': "Test Button Action", + 'url': 'https://www.example.net', + })