diff --git a/CHANGELOG b/CHANGELOG index 9ab02352b4..e140ace81d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +* Remove safe_eval +* Add ModelView.view_attributes +* Add pyson attribute on data field tag +* Changed into JSON: + - record rule domain + - trigger condition + - 'states', 'domain', 'spell' and 'colors' view attributes + - view domain + - 'email', 'domain', 'context', 'order' and 'search_value' action fields * Add product attribute on form view for One2Many * Remove float_time widget * Add TimeDelta field diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index a64eb5ccae..1675caf5cb 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -159,6 +159,12 @@ Class methods: Returns the window title used by the client for the specific view type. +.. classmethod:: ModelView.view_attributes() + + Returns a list of XPath, attribute and value. + Each element from the XPath will get the attribute set with the JSON + encoded value. + ============ ModelStorage ============ diff --git a/doc/topics/modules/index.rst b/doc/topics/modules/index.rst index 453af1f6ea..f1694ede4b 100644 --- a/doc/topics/modules/index.rst +++ b/doc/topics/modules/index.rst @@ -139,6 +139,9 @@ Here is the list of the tags: * ``eval``: Python code to evaluate and use result as value. + * ``pyson``: convert the evaluated value into :ref:`PYSON ` + string. + .. note:: Field content is considered as a string. So for fields that require other types, it is required to use the ``eval`` attribute. diff --git a/trytond/convert.py b/trytond/convert.py index 95d603bac5..1b30e9371f 100644 --- a/trytond/convert.py +++ b/trytond/convert.py @@ -7,10 +7,12 @@ import re from sql import Table from itertools import izip from collections import defaultdict +from decimal import Decimal from .version import VERSION -from .tools import safe_eval, grouped_slice +from .tools import grouped_slice from .transaction import Transaction +from .pyson import PYSONEncoder, CONTEXT logger = logging.getLogger(__name__) @@ -53,7 +55,7 @@ class MenuitemTagHandler: values[attr] = attributes.get(attr) if attributes.get('active'): - values['active'] = bool(safe_eval(attributes['active'])) + values['active'] = bool(eval(attributes['active'])) if values.get('parent'): values['parent'] = self.mh.get_id(values['parent']) @@ -208,12 +210,13 @@ class RecordTagHandler: search_attr = attributes.get('search', '') ref_attr = attributes.get('ref', '') eval_attr = attributes.get('eval', '') + pyson_attr = bool(int(attributes.get('pyson', '0'))) if search_attr: search_model = self.model._fields[field_name].model_name SearchModel = self.mh.pool.get(search_model) with Transaction().set_context(active_test=False): - found, = SearchModel.search(safe_eval(search_attr)) + found, = SearchModel.search(eval(search_attr)) self.values[field_name] = found.id elif ref_attr: @@ -225,7 +228,13 @@ class RecordTagHandler: context['version'] = VERSION.rsplit('.', 1)[0] context['ref'] = self.mh.get_id context['obj'] = lambda *a: 1 - self.values[field_name] = safe_eval(eval_attr, context) + context['Decimal'] = Decimal + if pyson_attr: + context.update(CONTEXT) + value = eval(eval_attr, context) + if pyson_attr: + value = PYSONEncoder().encode(value) + self.values[field_name] = value else: raise Exception("Tags '%s' not supported inside tag record." % @@ -534,16 +543,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler): elif field_type == 'reference': if not getattr(record, key): return None - elif not isinstance(getattr(record, key), basestring): - return str(getattr(record, key)) - ref_mode, ref_id = getattr(record, key).split(',', 1) - try: - ref_id = safe_eval(ref_id) - except Exception: - pass - if isinstance(ref_id, (list, tuple)): - ref_id = ref_id[0] - return ref_mode + ',' + str(ref_id) + return str(getattr(record, key)) elif field_type in ['one2many', 'many2many']: raise Unhandled_field("Unhandled field %s" % key) else: diff --git a/trytond/ir/action.py b/trytond/ir/action.py index d5c9477a89..9cde704ddf 100644 --- a/trytond/ir/action.py +++ b/trytond/ir/action.py @@ -10,9 +10,9 @@ from sql import Table from sql.aggregate import Count from ..model import ModelView, ModelStorage, ModelSQL, fields -from ..tools import file_open, safe_eval +from ..tools import file_open from .. import backend -from ..pyson import PYSONEncoder, CONTEXT, PYSON +from ..pyson import PYSONDecoder, PYSON from ..transaction import Transaction from ..pool import Pool from ..cache import Cache @@ -578,7 +578,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView): for report in reports: if report.email: try: - value = safe_eval(report.email, CONTEXT) + value = PYSONDecoder().decode(report.email) except Exception: value = None if isinstance(value, dict): @@ -644,14 +644,13 @@ class ActionReport(ActionMixin, ModelSQL, ModelView): @classmethod def get_pyson(cls, reports, name): pysons = {} - encoder = PYSONEncoder() field = name[6:] defaults = { 'email': '{}', } for report in reports: - pysons[report.id] = encoder.encode(safe_eval(getattr(report, field) - or defaults.get(field, 'None'), CONTEXT)) + pysons[report.id] = (getattr(report, field) + or defaults.get(field, 'null')) return pysons @classmethod @@ -811,7 +810,7 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView): if not domain: continue try: - value = safe_eval(domain, CONTEXT) + value = PYSONDecoder().decode(domain) except Exception: cls.raise_user_error('invalid_domain', { 'domain': domain, @@ -843,7 +842,7 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView): for action in actions: if action.context: try: - value = safe_eval(action.context, CONTEXT) + value = PYSONDecoder().decode(action.context) except Exception: cls.raise_user_error('invalid_context', { 'context': action.context, @@ -874,15 +873,12 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView): for view in self.act_window_views] def get_domains(self, name): - encoder = PYSONEncoder() - return [(domain.name, - encoder.encode(safe_eval(domain.domain or '[]', CONTEXT))) + return [(domain.name, domain.domain or '[]') for domain in self.act_window_domains] @classmethod def get_pyson(cls, windows, name): pysons = {} - encoder = PYSONEncoder() field = name[6:] defaults = { 'domain': '[]', @@ -890,8 +886,8 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView): 'search_value': '{}', } for window in windows: - pysons[window.id] = encoder.encode(safe_eval(getattr(window, field) - or defaults.get(field, 'None'), CONTEXT)) + pysons[window.id] = (getattr(window, field) + or defaults.get(field, 'null')) return pysons @classmethod @@ -970,11 +966,45 @@ class ActionActWindowDomain(ModelSQL, ModelView): def __setup__(cls): super(ActionActWindowDomain, cls).__setup__() cls._order.insert(0, ('sequence', 'ASC')) + cls._error_messages.update({ + 'invalid_domain': ('Invalid domain or search criteria ' + '"%(domain)s" on action "%(action)s".'), + }) @staticmethod def default_active(): return True + @classmethod + def validate(cls, actions): + super(ActionActWindowDomain, cls).validate(actions) + cls.check_domain(actions) + + @classmethod + def check_domain(cls, actions): + for action in actions: + if not action.domain: + continue + try: + value = PYSONDecoder().decode(action.domain) + except Exception: + value = None + if isinstance(value, PYSON): + if not value.types() == set([list]): + value = None + elif not isinstance(value, list): + value = None + else: + try: + fields.domain_validate(value) + except Exception: + value = None + if value is None: + cls.raise_user_error('invalid_domain', { + 'domain': action.domain, + 'action': action.rec_name, + }) + class ActionWizard(ActionMixin, ModelSQL, ModelView): "Action wizard" diff --git a/trytond/ir/lang.xml b/trytond/ir/lang.xml index 00b333d5fe..f6a953fe89 100644 --- a/trytond/ir/lang.xml +++ b/trytond/ir/lang.xml @@ -126,7 +126,7 @@ this repository contains the full copyright notices and license terms. --> Languages ir.lang - {'active_test': False} + diff --git a/trytond/ir/model.py b/trytond/ir/model.py index 6301321322..c77ff7d419 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -912,10 +912,9 @@ class ModelData(ModelSQL, ModelView): return dict(json.loads(values, object_hook=JSONDecoder())) except ValueError: # Migration from 3.2 - from ..tools import safe_eval from decimal import Decimal import datetime - return safe_eval(values, { + return eval(values, { 'Decimal': Decimal, 'datetime': datetime, }) diff --git a/trytond/ir/model.xml b/trytond/ir/model.xml index 9c5641ade8..ae4d69d879 100644 --- a/trytond/ir/model.xml +++ b/trytond/ir/model.xml @@ -204,7 +204,7 @@ this repository contains the full copyright notices and license terms. --> id="act_model_data_form_domain_out_of_sync"> Out of Sync - [('out_of_sync', '=', True)] + Modules ir.action.act_window ir.module.module - [('name', '!=', 'tests')] + diff --git a/trytond/ir/property.xml b/trytond/ir/property.xml index a7e1718a68..2ea792e1be 100644 --- a/trytond/ir/property.xml +++ b/trytond/ir/property.xml @@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. --> Default Properties ir.action.act_window ir.property - [('res', '=', None)] + diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py index f3e1b9c644..351b577248 100644 --- a/trytond/ir/rule.py +++ b/trytond/ir/rule.py @@ -1,11 +1,11 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from ..model import ModelView, ModelSQL, fields -from ..tools import safe_eval +from ..model import ModelView, ModelSQL, fields, EvalEnvironment from ..transaction import Transaction from ..cache import Cache from ..pool import Pool from .. import backend +from ..pyson import PYSONDecoder __all__ = [ 'RuleGroup', 'Rule', @@ -95,7 +95,8 @@ class Rule(ModelSQL, ModelView): rule_group = fields.Many2One('ir.rule.group', 'Group', select=True, required=True, ondelete="CASCADE") domain = fields.Char('Domain', required=True, - help='Domain is evaluated with "user" as the current user') + help='Domain is evaluated with a PYSON context containing:\n' + '- "user" as the current user') _domain_get_cache = Cache('ir_rule.domain_get', context=False) @classmethod @@ -126,7 +127,7 @@ class Rule(ModelSQL, ModelView): ctx = cls._get_context() for rule in rules: try: - value = safe_eval(rule.domain, ctx) + value = PYSONDecoder(ctx).decode(rule.domain) except Exception: cls.raise_user_error('invalid_domain', (rule.rec_name,)) if not isinstance(value, list): @@ -142,7 +143,7 @@ class Rule(ModelSQL, ModelView): User = Pool().get('res.user') user_id = Transaction().user with Transaction().set_context(_check_access=False): - user = User(user_id) + user = EvalEnvironment(User(user_id), User) return { 'user': user, } @@ -218,7 +219,7 @@ class Rule(ModelSQL, ModelView): for rule in cls.browse(ids): assert rule.domain, ('Rule domain empty,' 'check if migration was done') - dom = safe_eval(rule.domain, ctx) + dom = PYSONDecoder(ctx).decode(rule.domain) if rule.rule_group.global_p: clause_global.setdefault(rule.rule_group.id, ['OR']) clause_global[rule.rule_group.id].append(dom) diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py index aa7d33257b..33d0c18bec 100644 --- a/trytond/ir/sequence.py +++ b/trytond/ir/sequence.py @@ -173,6 +173,18 @@ class Sequence(ModelSQL, ModelView): 'number_next_internal': value, }) + @classmethod + def view_attributes(cls): + return [ + ('//group[@id="incremental"]', 'states', { + 'invisible': ~Eval('type').in_(['incremental']), + }), + ('//group[@id="timestamp"]', 'states', { + 'invisible': ~Eval('type').in_( + ['decimal timestamp', 'hexadecimal timestamp']), + }), + ] + @classmethod def create(cls, vlist): sequences = super(Sequence, cls).create(vlist) diff --git a/trytond/ir/sequence.xml b/trytond/ir/sequence.xml index be5d9960e0..c9c3506813 100644 --- a/trytond/ir/sequence.xml +++ b/trytond/ir/sequence.xml @@ -20,7 +20,7 @@ this repository contains the full copyright notices and license terms. --> Sequences ir.action.act_window ir.sequence - {'active_test': False} + @@ -51,7 +51,7 @@ this repository contains the full copyright notices and license terms. --> Sequences Strict ir.action.act_window ir.sequence.strict - {'active_test': False} + diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 4c734d8f79..a7be694b04 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -24,7 +24,7 @@ from ..wizard import Wizard, StateView, StateTransition, StateAction, \ Button from ..tools import file_open, reduce_ids, grouped_slice from .. import backend -from ..pyson import PYSONEncoder +from ..pyson import PYSONEncoder, Eval from ..transaction import Transaction from ..pool import Pool from ..cache import Cache @@ -372,6 +372,10 @@ class Translation(ModelSQL, ModelView): def get_src_md5(cls, src): return md5((src or '').encode('utf-8')).hexdigest() + @classmethod + def view_attributes(cls): + return [('/form//field[@name="value"]', 'spell', Eval('lang'))] + @classmethod def get_ids(cls, name, ttype, lang, ids): "Return translation for each id" diff --git a/trytond/ir/translation.xml b/trytond/ir/translation.xml index e93b30bee1..fbb8424ba1 100644 --- a/trytond/ir/translation.xml +++ b/trytond/ir/translation.xml @@ -16,7 +16,7 @@ this repository contains the full copyright notices and license terms. --> Translations ir.translation - [('module', '!=', None)] + diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py index f83729512b..704400d8d6 100644 --- a/trytond/ir/trigger.py +++ b/trytond/ir/trigger.py @@ -5,9 +5,9 @@ import time from sql import Literal, Null from sql.aggregate import Count, Max -from ..model import ModelView, ModelSQL, fields -from ..pyson import Eval -from ..tools import safe_eval, grouped_slice +from ..model import ModelView, ModelSQL, fields, EvalEnvironment +from ..pyson import Eval, PYSONDecoder +from ..tools import grouped_slice from .. import backend from ..tools import reduce_ids from ..transaction import Transaction @@ -171,8 +171,8 @@ class Trigger(ModelSQL, ModelView): env['current_date'] = datetime.datetime.today() env['time'] = time env['context'] = Transaction().context - env['self'] = record - return bool(safe_eval(trigger.condition, env)) + env['self'] = EvalEnvironment(record, record.__class__) + return bool(PYSONDecoder(env).decode(trigger.condition)) @classmethod def trigger_action(cls, records, trigger): diff --git a/trytond/ir/ui/menu.xml b/trytond/ir/ui/menu.xml index 4a0a610471..7b11a1d0c5 100644 --- a/trytond/ir/ui/menu.xml +++ b/trytond/ir/ui/menu.xml @@ -28,7 +28,7 @@ this repository contains the full copyright notices and license terms. --> Menu ir.ui.menu menu - [('parent', '=', None)] + diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py index 04de1732e8..72a98cf0dd 100644 --- a/trytond/ir/ui/view.py +++ b/trytond/ir/ui/view.py @@ -12,7 +12,7 @@ from lxml import etree from trytond.model import ModelView, ModelSQL, fields from trytond import backend from trytond.pyson import CONTEXT, Eval, Bool, PYSONDecoder -from trytond.tools import safe_eval, file_open +from trytond.tools import file_open from trytond.transaction import Transaction from trytond.wizard import Wizard, StateView, Button from trytond.pool import Pool @@ -155,7 +155,7 @@ class View(ModelSQL, ModelView): for attr in ('states', 'domain', 'spell', 'colors'): if element.get(attr): try: - value = safe_eval(element.get(attr), CONTEXT) + value = PYSONDecoder().decode(element.get(attr)) validates.get(attr, lambda a: True)(value) except Exception, e: logger.error('Invalid pyson view element "%s:%s":' diff --git a/trytond/ir/view/module_list.xml b/trytond/ir/view/module_list.xml index cf60347528..921f2248d0 100644 --- a/trytond/ir/view/module_list.xml +++ b/trytond/ir/view/module_list.xml @@ -1,8 +1,7 @@ - + diff --git a/trytond/ir/view/sequence_form.xml b/trytond/ir/view/sequence_form.xml index 9b96b59fa7..13b183455e 100644 --- a/trytond/ir/view/sequence_form.xml +++ b/trytond/ir/view/sequence_form.xml @@ -14,8 +14,7 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -432,11 +434,13 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -444,11 +448,13 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -457,11 +463,13 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -470,11 +478,13 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -483,11 +493,13 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -496,11 +508,13 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + - [('groups', '=', None)] + @@ -760,7 +774,9 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + @@ -770,7 +786,9 @@ this repository contains the full copyright notices and license terms. --> - [('groups', 'in', [g.id for g in user.groups])] + @@ -824,7 +842,9 @@ this repository contains the full copyright notices and license terms. --> - [('user', '=', user.id)] + @@ -850,7 +870,9 @@ this repository contains the full copyright notices and license terms. --> - [('create_uid', '=', user.id)] + @@ -860,7 +882,9 @@ this repository contains the full copyright notices and license terms. --> - [('create_uid', '=', user.id)] + @@ -870,7 +894,9 @@ this repository contains the full copyright notices and license terms. --> - [('user', '=', user.id)] + diff --git a/trytond/res/user.xml b/trytond/res/user.xml index 5d39768214..1c2993fcc9 100644 --- a/trytond/res/user.xml +++ b/trytond/res/user.xml @@ -101,7 +101,8 @@ this repository contains the full copyright notices and license terms. --> - [('user', '=', user.id)] + diff --git a/trytond/tests/test_tools.py b/trytond/tests/test_tools.py index f34b48db35..0b26991792 100644 --- a/trytond/tests/test_tools.py +++ b/trytond/tests/test_tools.py @@ -8,7 +8,7 @@ import datetime import sql import sql.operators -from trytond.tools import reduce_ids, safe_eval, datetime_strftime, \ +from trytond.tools import reduce_ids, datetime_strftime, \ reduce_domain, decimal_ @@ -59,24 +59,6 @@ class ToolsTestCase(unittest.TestCase): | (self.table.id.in_([15.0, 18.0, 19.0, 21.0])))) self.assertRaises(AssertionError, reduce_ids, self.table.id, [1.1]) - def test0060safe_eval_builtin(self): - 'Attempt to access a unsafe builtin' - self.assertRaises(NameError, safe_eval, "open('test.txt', 'w')") - - def test0061safe_eval_getattr(self): - 'Attempt to get arround direct attr access' - self.assertRaises(NameError, safe_eval, "getattr(int, 'real')") - - def test0062safe_eval_func_globals(self): - 'Attempt to access global enviroment where fun was defined' - self.assertRaises(SyntaxError, safe_eval, - "def x(): pass; print x.func_globals") - - def test0063safe_eval_lowlevel(self): - "Lowlevel tricks to access 'object'" - self.assertRaises(ValueError, safe_eval, - "().__class__.mro()[1].__subclasses__()") - def test0070datetime_strftime(self): 'Test datetime_strftime' self.assert_(datetime_strftime(datetime.date(2005, 3, 2), diff --git a/trytond/tests/test_trigger.py b/trytond/tests/test_trigger.py index ca87d7385e..4f247412ae 100644 --- a/trytond/tests/test_trigger.py +++ b/trytond/tests/test_trigger.py @@ -11,6 +11,7 @@ from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \ from trytond.tests.trigger import TRIGGER_LOGS from trytond.transaction import Transaction from trytond.exceptions import UserError +from trytond.pyson import PYSONEncoder, Eval class TriggerTestCase(unittest.TestCase): @@ -37,7 +38,7 @@ class TriggerTestCase(unittest.TestCase): 'name': 'Test', 'model': model.id, 'on_time': True, - 'condition': 'True', + 'condition': 'true', 'action_model': action_model.id, 'action_function': 'test', } @@ -80,7 +81,7 @@ class TriggerTestCase(unittest.TestCase): 'name': 'Test', 'model': model.id, 'on_create': True, - 'condition': 'True', + 'condition': 'true', 'action_model': action_model.id, 'action_function': 'trigger', }]) @@ -93,8 +94,10 @@ class TriggerTestCase(unittest.TestCase): TRIGGER_LOGS.pop() # Trigger with condition + condition = PYSONEncoder().encode( + Eval('self', {}).get('name') == 'Bar') self.trigger.write([trigger], { - 'condition': 'self.name == "Bar"', + 'condition': condition, }) # Matching condition @@ -112,7 +115,7 @@ class TriggerTestCase(unittest.TestCase): # With limit number self.trigger.write([trigger], { - 'condition': 'True', + 'condition': 'true', 'limit_number': 1, }) triggered, = self.triggered.create([{ @@ -151,7 +154,7 @@ class TriggerTestCase(unittest.TestCase): 'name': 'Test', 'model': model.id, 'on_write': True, - 'condition': 'True', + 'condition': 'true', 'action_model': action_model.id, 'action_function': 'trigger', }]) @@ -166,8 +169,10 @@ class TriggerTestCase(unittest.TestCase): self.assertEqual(TRIGGER_LOGS, []) # Trigger with condition + condition = PYSONEncoder().encode( + Eval('self', {}).get('name') == 'Bar') self.trigger.write([trigger], { - 'condition': 'self.name == "Bar"', + 'condition': condition, }) # Matching condition @@ -190,8 +195,10 @@ class TriggerTestCase(unittest.TestCase): self.assertEqual(TRIGGER_LOGS, []) # With limit number + condition = PYSONEncoder().encode( + Eval('self', {}).get('name') == 'Bar') self.trigger.write([trigger], { - 'condition': 'self.name == "Bar"', + 'condition': condition, 'limit_number': 1, }) triggered, = self.triggered.create([{ @@ -266,7 +273,7 @@ class TriggerTestCase(unittest.TestCase): 'name': 'Test', 'model': model.id, 'on_delete': True, - 'condition': 'True', + 'condition': 'true', 'action_model': action_model.id, 'action_function': 'trigger', }]) @@ -277,8 +284,10 @@ class TriggerTestCase(unittest.TestCase): Transaction().delete = {} # Trigger with condition + condition = PYSONEncoder().encode( + Eval('self', {}).get('name') == 'Bar') self.trigger.write([trigger], { - 'condition': 'self.name == "Bar"', + 'condition': condition, }) triggered, = self.triggered.create([{ @@ -306,7 +315,7 @@ class TriggerTestCase(unittest.TestCase): # With limit number self.trigger.write([trigger], { - 'condition': 'True', + 'condition': 'true', 'limit_number': 1, }) self.triggered.delete([triggered]) @@ -351,7 +360,7 @@ class TriggerTestCase(unittest.TestCase): 'name': 'Test', 'model': model.id, 'on_time': True, - 'condition': 'True', + 'condition': 'true', 'action_model': action_model.id, 'action_function': 'trigger', }]) @@ -364,8 +373,10 @@ class TriggerTestCase(unittest.TestCase): TRIGGER_LOGS.pop() # Trigger with condition + condition = PYSONEncoder().encode( + Eval('self', {}).get('name') == 'Bar') self.trigger.write([trigger], { - 'condition': 'self.name == "Bar"', + 'condition': condition, }) # Matching condition @@ -385,7 +396,7 @@ class TriggerTestCase(unittest.TestCase): # With limit number self.trigger.write([trigger], { - 'condition': 'True', + 'condition': 'true', 'limit_number': 1, }) self.trigger.trigger_time() diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py index 25ad99d518..483de8c1bd 100644 --- a/trytond/tools/misc.py +++ b/trytond/tools/misc.py @@ -9,8 +9,6 @@ import sys import subprocess from threading import local import smtplib -import dis -from decimal import Decimal from array import array from itertools import islice from sql import Literal @@ -333,58 +331,6 @@ def reduce_ids(field, ids): sql.append(field.in_(discontinue_list)) return sql -_ALLOWED_CODES = set(dis.opmap[x] for x in [ - 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'BUILD_LIST', - 'BUILD_MAP', 'BUILD_TUPLE', 'LOAD_CONST', 'RETURN_VALUE', - 'STORE_SUBSCR', 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', - 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY', 'BINARY_DIVIDE', - 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE', 'BINARY_MODULO', - 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_LSHIFT', 'BINARY_RSHIFT', - 'BINARY_AND', 'BINARY_XOR', 'BINARY_OR', 'STORE_MAP', 'LOAD_NAME', - 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR', 'STORE_NAME', 'GET_ITER', - 'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE', 'DELETE_NAME', - 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_IF_FALSE_OR_POP', - 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', 'POP_JUMP_IF_TRUE', - 'BINARY_SUBSCR', 'JUMP_FORWARD', - ] if x in dis.opmap) - - -@memoize(1000) -def _compile_source(source): - comp = compile(source, '', 'eval') - codes = [] - co_code = comp.co_code - i = 0 - while i < len(co_code): - code = ord(co_code[i]) - codes.append(code) - if code >= dis.HAVE_ARGUMENT: - i += 3 - else: - i += 1 - for code in codes: - if code not in _ALLOWED_CODES: - raise ValueError('opcode %s not allowed' % dis.opname[code]) - return comp - - -def safe_eval(source, data=None): - if '__' in source: - raise ValueError('Double underscores not allowed') - - comp = _compile_source(source) - return eval(comp, {'__builtins__': { - 'True': True, - 'False': False, - 'str': str, - 'globals': locals, - 'locals': locals, - 'bool': bool, - 'dict': dict, - 'round': round, - 'Decimal': Decimal, - }}, data) - def reduce_domain(domain): ''' diff --git a/trytond/tryton.rnc b/trytond/tryton.rnc index 3c46d63ec5..fe87698000 100644 --- a/trytond/tryton.rnc +++ b/trytond/tryton.rnc @@ -18,6 +18,7 @@ attlist.field &= attribute name { text } attlist.field &= attribute search { text }? attlist.field &= attribute ref { text }? attlist.field &= attribute eval { text }? +attlist.field &= attribute pyson { "0" | "1" }? attlist.field &= attribute type { "xml" }? menuitem = element menuitem { attlist.menuitem, empty } attlist.menuitem &= attribute id { text } diff --git a/trytond/webdav/webdav.xml b/trytond/webdav/webdav.xml index 2a6ab872f3..5345da9bc6 100644 --- a/trytond/webdav/webdav.xml +++ b/trytond/webdav/webdav.xml @@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. --> Collections ir.action.act_window webdav.collection - [('parent', '=', None)] + @@ -116,7 +116,9 @@ this repository contains the full copyright notices and license terms. --> - [('user', '=', user.id)] +