changeset 2bec62d40123 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=2bec62d40123
description:
        Add model, record and records attribute on Wizard

        issue9421
        review309861012
diffstat:

 CHANGELOG                    |   2 +
 doc/ref/wizard.rst           |  15 ++++++++++++++
 trytond/ir/action.py         |  11 ++++++++++
 trytond/ir/message.xml       |   8 +++++++
 trytond/ir/translation.py    |  30 +++++++++++-----------------
 trytond/tests/test_wizard.py |  26 ++++++++++++++++++++++++
 trytond/tests/wizard.xml     |   6 +++++
 trytond/wizard/wizard.py     |  46 ++++++++++++++++++++++++++++++++++++++++---
 8 files changed, 122 insertions(+), 22 deletions(-)

diffs (289 lines):

diff -r 93f83766e125 -r 2bec62d40123 CHANGELOG
--- a/CHANGELOG Sat Jul 04 23:38:22 2020 +0200
+++ b/CHANGELOG Tue Jul 07 23:56:10 2020 +0200
@@ -1,3 +1,5 @@
+* Add model, record and records attributes on Wizard
+* Check read access of wizard records
 * Add cached_property in tools
 * Use custom class to store record data
 * Do not write to existing targets on xxx2Many add
diff -r 93f83766e125 -r 2bec62d40123 doc/ref/wizard.rst
--- a/doc/ref/wizard.rst        Sat Jul 04 23:38:22 2020 +0200
+++ b/doc/ref/wizard.rst        Tue Jul 07 23:56:10 2020 +0200
@@ -45,6 +45,21 @@
     It contains a dictionary with state name as key and :class:`State` as
     value.
 
+.. attribute:: model
+
+    It contains the :class:`~trytond.model.Model` class on which the wizard is
+    executed.
+
+.. attribute:: record
+
+    It contains the :class:`~trytond.model.Model` instance on which the wizard
+    is executed.
+
+.. attribute:: records
+
+    It contains the list of :class:`~trytond.model.Model` instances on which
+    the wizard is executed.
+
 Class methods are:
 
 .. classmethod:: Wizard.__setup__()
diff -r 93f83766e125 -r 2bec62d40123 trytond/ir/action.py
--- a/trytond/ir/action.py      Sat Jul 04 23:38:22 2020 +0200
+++ b/trytond/ir/action.py      Tue Jul 07 23:56:10 2020 +0200
@@ -1046,6 +1046,17 @@
     def default_type():
         return 'ir.action.wizard'
 
+    @classmethod
+    def get_models(cls, name, action_id=None):
+        # TODO add cache
+        domain = [
+            (cls._action_name, '=', name),
+            ]
+        if action_id:
+            domain.append(('id', '=', action_id))
+        actions = cls.search(domain)
+        return {a.model for a in actions if a.model}
+
 
 class ActionURL(ActionMixin, ModelSQL, ModelView):
     "Action URL"
diff -r 93f83766e125 -r 2bec62d40123 trytond/ir/message.xml
--- a/trytond/ir/message.xml    Sat Jul 04 23:38:22 2020 +0200
+++ b/trytond/ir/message.xml    Tue Jul 07 23:56:10 2020 +0200
@@ -311,5 +311,13 @@
         <record model="ir.message" id="msg_notes">
             <field name="text">Notes</field>
         </record>
+
+        <record model="ir.message" id="msg_access_wizard_error">
+            <field name="text">You are not allowed to execute wizard 
"%(wizard)s".</field>
+        </record>
+        <record model="ir.message" id="msg_access_wizard_model_error">
+            <field name="text">You are not allowed to execute wizard 
"%(wizard)s" on "%(model)s".</field>
+        </record>
+
     </data>
 </tryton>
diff -r 93f83766e125 -r 2bec62d40123 trytond/ir/translation.py
--- a/trytond/ir/translation.py Sat Jul 04 23:38:22 2020 +0200
+++ b/trytond/ir/translation.py Tue Jul 07 23:56:10 2020 +0200
@@ -950,11 +950,10 @@
         pool = Pool()
         Report = pool.get('ir.action.report')
         Translation = pool.get('ir.translation')
-        context = Transaction().context
 
-        if context.get('active_model') == Report.__name__:
-            reports = Report.browse(context.get('active_ids', []))
-        elif context.get('active_model', 'ir.ui.menu') == 'ir.ui.menu':
+        if self.model == Report:
+            reports = self.records
+        elif not self.model or self.model.__name__ == 'ir.ui.menu':
             with Transaction().set_context(active_test=False):
                 reports = Report.search([('translatable', '=', True)])
         else:
@@ -1045,11 +1044,10 @@
         pool = Pool()
         View = pool.get('ir.ui.view')
         Translation = pool.get('ir.translation')
-        context = Transaction().context
 
-        if context.get('active_model') == View.__name__:
-            views = View.browse(context.get('active_ids', []))
-        elif context.get('active_model', 'ir.ui.menu') == 'ir.ui.menu':
+        if self.model == View:
+            views = self.records
+        elif not self.model or self.model.__name__ == 'ir.ui.menu':
             with Transaction().set_context(active_test=False):
                 views = View.search([])
         else:
@@ -1357,19 +1355,18 @@
         Translation = pool.get('ir.translation')
         Report = pool.get('ir.action.report')
         View = pool.get('ir.ui.view')
-        context = Transaction().context
         cursor = Transaction().connection.cursor()
         cursor_update = Transaction().connection.cursor()
         translation = Translation.__table__()
         lang = self.start.language.code
         parent_lang = get_parent(lang)
 
-        if context.get('active_model') == Report.__name__:
-            reports = Report.browse(context.get('active_ids', []))
+        if self.model == Report:
+            reports = self.records
             source_clause = ((translation.type == 'report')
                 & translation.name.in_([r.report_name for r in reports]))
-        elif context.get('active_model') == View.__name__:
-            views = View.browse(context.get('active_ids', []))
+        elif self.model == View:
+            views = self.records
             source_clause = ((translation.type == 'view')
                 & translation.name.in_([v.model for v in views]))
         else:
@@ -1420,7 +1417,7 @@
                         & (translation.module == row['module'])
                         & (translation.lang == lang)))
 
-        if context.get('active_model') in {Report.__name__, View.__name__}:
+        if self.model in {Report, View}:
             return
 
         columns = [translation.name.as_('name'),
@@ -1592,11 +1589,8 @@
     open_ = StateAction('ir.act_translation_report')
 
     def do_open_(self, action):
-        pool = Pool()
-        Report = pool.get('ir.action.report')
         context = Transaction().context
-        assert context['active_model'] == Report.__name__
-        reports = Report.browse(context['active_ids'])
+        reports = self.records
         action['pyson_domain'] = PYSONEncoder().encode([
                 ('type', '=', 'report'),
                 ('name', 'in', [r.report_name for r in reports]),
diff -r 93f83766e125 -r 2bec62d40123 trytond/tests/test_wizard.py
--- a/trytond/tests/test_wizard.py      Sat Jul 04 23:38:22 2020 +0200
+++ b/trytond/tests/test_wizard.py      Tue Jul 07 23:56:10 2020 +0200
@@ -127,6 +127,32 @@
             with Transaction().set_context(active_model='test.access'):
                 Wizard.execute(session_id, {}, start_state)
 
+    @with_transaction()
+    def test_execute_without_read_access(self):
+        "Execute wizard without read access"
+        pool = Pool()
+        Wizard = pool.get('test.test_wizard', type='wizard')
+
+        session_id, start_state, end_state = Wizard.create()
+
+        with self.assertRaises(AccessError):
+            with Transaction().set_context(
+                    active_model='test.access', active_id=1):
+                Wizard.execute(session_id, {}, start_state)
+
+    @with_transaction()
+    def test_execute_wrong_model(self):
+        "Execute wizard on wrong model"
+        pool = Pool()
+        Wizard = pool.get('test.test_wizard', type='wizard')
+
+        session_id, start_state, end_state = Wizard.create()
+
+        with self.assertRaises(AccessError):
+            with Transaction().set_context(
+                    active_model='test.test_wizard.start'):
+                Wizard.execute(session_id, {}, start_state)
+
 
 def suite():
     return unittest.TestLoader().loadTestsFromTestCase(WizardTestCase)
diff -r 93f83766e125 -r 2bec62d40123 trytond/tests/wizard.xml
--- a/trytond/tests/wizard.xml  Sat Jul 04 23:38:22 2020 +0200
+++ b/trytond/tests/wizard.xml  Tue Jul 07 23:56:10 2020 +0200
@@ -15,5 +15,11 @@
                 ]]>
             </field>
         </record>
+
+        <record model="ir.action.wizard" id="test_wizard">
+            <field name="name">Test Wizard</field>
+            <field name="wiz_name">test.test_wizard</field>
+            <field name="model">test.access</field>
+        </record>
     </data>
 </tryton>
diff -r 93f83766e125 -r 2bec62d40123 trytond/wizard/wizard.py
--- a/trytond/wizard/wizard.py  Sat Jul 04 23:38:22 2020 +0200
+++ b/trytond/wizard/wizard.py  Tue Jul 07 23:56:10 2020 +0200
@@ -8,15 +8,17 @@
 import json
 import copy
 
+from trytond.i18n import gettext
 from trytond.pool import Pool, PoolBase
 from trytond.transaction import Transaction
 from trytond.url import URLMixin
 from trytond.protocols.jsonrpc import JSONDecoder, JSONEncoder
 from trytond.model import ModelSQL
+from trytond.model.exceptions import AccessError
 from trytond.model.fields import states_validate
 from trytond.pyson import PYSONEncoder
 from trytond.rpc import RPC
-from trytond.exceptions import UserError
+from trytond.tools import cached_property
 
 
 class Button(object):
@@ -233,21 +235,38 @@
 
         with Transaction().set_context(_check_access=True):
             model = context.get('active_model')
+            if model:
+                Model = pool.get(model)
             if model and model != 'ir.ui.menu':
                 ModelAccess.check(model, 'read')
+            models = ActionWizard.get_models(
+                cls.__name__, action_id=context.get('action_id'))
+            if model and models and model not in models:
+                raise AccessError(gettext(
+                        'ir.msg_access_wizard_model_error',
+                        wizard=cls.__name__,
+                        model=model))
             groups = set(User.get_groups())
             wizard_groups = ActionWizard.get_groups(cls.__name__,
                 action_id=context.get('action_id'))
             if wizard_groups:
                 if not groups & wizard_groups:
-                    raise UserError('Calling wizard %s is not allowed!'
-                        % cls.__name__)
+                    raise AccessError(gettext(
+                            'ir.msg_access_wizard_error',
+                            name=cls.__name__))
             elif model and model != 'ir.ui.menu':
-                Model = pool.get(model)
                 if (not callable(getattr(Model, 'table_query', None))
                         or Model.write.__func__ != ModelSQL.write.__func__):
                     ModelAccess.check(model, 'write')
 
+            if model:
+                ids = context.get('active_ids') or []
+                id_ = context.get('active_id')
+                if id_ not in ids:
+                    ids.append(id_)
+                # Check read access
+                Model.read(ids, ['id'])
+
     @classmethod
     def create(cls):
         "Create a session"
@@ -354,3 +373,22 @@
             Session.write([session], {
                     'data': data,
                     })
+
+    @cached_property
+    def model(self):
+        pool = Pool()
+        context = Transaction().context
+        if context.get('active_model'):
+            return pool.get(context['active_model'])
+
+    @cached_property
+    def record(self):
+        context = Transaction().context
+        if context.get('active_id'):
+            return self.model(context['active_id'])
+
+    @cached_property
+    def records(self):
+        context = Transaction().context
+        if context.get('active_ids'):
+            return self.model.browse(context['active_ids'])

Reply via email to