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'])