Remove safe_eval

We convert all the fields that were storing python code into PYSON.
We add a pyson attribute on field tag to store PYSON value.

issue4416
review6971002
This commit is contained in:
Cédric Krier
2015-03-09 19:40:41 +01:00
parent fbda6e83a9
commit bcd28ca071
32 changed files with 226 additions and 184 deletions
+9
View File
@@ -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
+6
View File
@@ -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
============
+3
View File
@@ -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 <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.
+14 -14
View File
@@ -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:
+44 -14
View File
@@ -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"
+1 -1
View File
@@ -126,7 +126,7 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window" id="act_lang_form">
<field name="name">Languages</field>
<field name="res_model">ir.lang</field>
<field name="context">{'active_test': False}</field>
<field name="context" eval="{'active_test': False}" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_lang_form_view1">
+1 -2
View File
@@ -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,
})
+1 -1
View File
@@ -204,7 +204,7 @@ this repository contains the full copyright notices and license terms. -->
id="act_model_data_form_domain_out_of_sync">
<field name="name">Out of Sync</field>
<field name="sequence" eval="10"/>
<field name="domain">[('out_of_sync', '=', True)]</field>
<field name="domain" eval="[('out_of_sync', '=', True)]" pyson="1"/>
<field name="act_window" ref="act_model_data_form"/>
</record>
<record model="ir.action.act_window.domain"
+10 -1
View File
@@ -9,7 +9,7 @@ from trytond.wizard import Wizard, StateView, Button, StateTransition, \
from trytond import backend
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.pyson import Eval
from trytond.pyson import Eval, If
from trytond.rpc import RPC
__all__ = [
@@ -120,6 +120,15 @@ class Module(ModelSQL, ModelView):
child_ids[name2id[dep.name]].append(child.id)
return child_ids
@classmethod
def view_attributes(cls):
return [('/tree', 'colors',
If(Eval('state').in_(['to upgrade', 'to install']),
'blue',
If(Eval('state') == 'uninstalled',
'grey',
'black')))]
@classmethod
def delete(cls, records):
for module in records:
+1 -1
View File
@@ -18,7 +18,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">Modules</field>
<field name="type">ir.action.act_window</field>
<field name="res_model">ir.module.module</field>
<field name="domain">[('name', '!=', 'tests')]</field>
<field name="domain" eval="[('name', '!=', 'tests')]" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_module_form_view1">
+1 -1
View File
@@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">Default Properties</field>
<field name="type">ir.action.act_window</field>
<field name="res_model">ir.property</field>
<field name="domain">[('res', '=', None)]</field>
<field name="domain" eval="[('res', '=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_property_form_view1_default">
+7 -6
View File
@@ -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)
+12
View File
@@ -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)
+2 -2
View File
@@ -20,7 +20,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">Sequences</field>
<field name="type">ir.action.act_window</field>
<field name="res_model">ir.sequence</field>
<field name="context">{'active_test': False}</field>
<field name="context" eval="{'active_test': False}" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_sequence_form_view1">
@@ -51,7 +51,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">Sequences Strict</field>
<field name="type">ir.action.act_window</field>
<field name="res_model">ir.sequence.strict</field>
<field name="context">{'active_test': False}</field>
<field name="context" eval="{'active_test': False}" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_sequence_strict_form_view1">
+5 -1
View File
@@ -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"
+1 -1
View File
@@ -16,7 +16,7 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window" id="act_translation_form">
<field name="name">Translations</field>
<field name="res_model">ir.translation</field>
<field name="domain">[('module', '!=', None)]</field>
<field name="domain" eval="[('module', '!=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_translation_form_view1">
+5 -5
View File
@@ -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):
+1 -1
View File
@@ -28,7 +28,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">Menu</field>
<field name="res_model">ir.ui.menu</field>
<field name="usage">menu</field>
<field name="domain">[('parent', '=', None)]</field>
<field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_menu_tree_view1">
+2 -2
View File
@@ -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":'
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree string="Modules" on_write="on_write"
colors="If(In(Eval('state', ''), ['to upgrade', 'to install']), 'blue', If(Equal(Eval('state', ''), 'uninstalled'), 'grey', 'black'))">
<tree string="Modules" on_write="on_write">
<field name="name" expand="1"/>
<field name="version"/>
<field name="state"/>
+2 -4
View File
@@ -14,8 +14,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="suffix"/>
<label name="type"/>
<field name="type"/>
<group string="Incremental" id="incremental" colspan="4"
states="{'invisible': Not(In(Eval('type'), ['incremental']))}">
<group string="Incremental" id="incremental" colspan="4">
<label name="padding"/>
<field name="padding" />
<label name="number_increment"/>
@@ -23,8 +22,7 @@ this repository contains the full copyright notices and license terms. -->
<label name="number_next"/>
<field name="number_next"/>
</group>
<group string="Timestamp" id="timestamp" colspan="4"
states="{'invisible': Not(In(Eval('type'), ['decimal timestamp', 'hexadecimal timestamp']))}">
<group string="Timestamp" id="timestamp" colspan="4">
<label name="timestamp_rounding"/>
<field name="timestamp_rounding"/>
<label name="timestamp_offset"/>
+1 -1
View File
@@ -20,6 +20,6 @@ this repository contains the full copyright notices and license terms. -->
<separator name="src"/>
<separator name="value"/>
<field name="src"/>
<field name="value" spell="Eval('lang')"/>
<field name="value"/>
</group>
</form>
+3 -2
View File
@@ -2,7 +2,7 @@
# this repository contains the full copyright notices and license terms.
from .model import Model
from .modelview import ModelView
from .modelstorage import ModelStorage
from .modelstorage import ModelStorage, EvalEnvironment
from .modelsingleton import ModelSingleton
from .modelsql import ModelSQL
from .workflow import Workflow
@@ -12,4 +12,5 @@ from .union import UnionMixin
from .descriptors import dualmethod
__all__ = ['Model', 'ModelView', 'ModelStorage', 'ModelSingleton', 'ModelSQL',
'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin', 'dualmethod']
'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin', 'dualmethod',
'EvalEnvironment']
+1 -1
View File
@@ -30,7 +30,7 @@ from trytond.rpc import RPC
from .modelview import ModelView
from .descriptors import dualmethod
__all__ = ['ModelStorage']
__all__ = ['ModelStorage', 'EvalEnvironment']
def cache_size():
+14 -12
View File
@@ -6,8 +6,8 @@ import copy
import collections
from trytond.model import Model, fields
from trytond.tools import safe_eval, ClassProperty
from trytond.pyson import PYSONEncoder, CONTEXT
from trytond.tools import ClassProperty
from trytond.pyson import PYSONDecoder, PYSONEncoder
from trytond.transaction import Transaction
from trytond.cache import Cache
from trytond.pool import Pool
@@ -284,8 +284,8 @@ class ModelView(Model):
raise_p = True
for view in views:
if view.domain:
if not safe_eval(view.domain,
{'context': Transaction().context}):
if not PYSONDecoder({'context': Transaction().context}
).decode(view.domain):
continue
if not view.arch or not view.arch.strip():
continue
@@ -371,12 +371,22 @@ class ModelView(Model):
"""
return value
@classmethod
def view_attributes(cls):
'Return a list of xpath, attribute name and value'
return []
@classmethod
def _view_look_dom_arch(cls, tree, type, field_children=None):
pool = Pool()
ModelAccess = pool.get('ir.model.access')
FieldAccess = pool.get('ir.model.field.access')
encoder = PYSONEncoder()
for xpath, attribute, value in cls.view_attributes():
for element in tree.xpath(xpath):
element.set(attribute, encoder.encode(value))
fields_width = {}
tree_root = tree.getroottree().getroot()
@@ -516,15 +526,7 @@ class ModelView(Model):
if element.get('name') in fields_width:
element.set('width', str(fields_width[element.get('name')]))
# convert attributes into pyson
encoder = PYSONEncoder()
for attr in ('states', 'domain', 'spell', 'colors'):
if (element.get(attr)
# Avoid double evaluation from inherit with different model
and '__' not in element.get(attr)):
element.set(attr, encoder.encode(safe_eval(element.get(attr),
CONTEXT)))
if element.tag == 'button':
button_name = element.attrib['name']
if button_name in cls._buttons:
+46 -20
View File
@@ -420,11 +420,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_menu1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_menu"/>
</record>
<record model="ir.rule" id="rule_menu2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_menu"/>
</record>
<record model="ir.rule.group" id="rule_group_action">
@@ -432,11 +434,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_action1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action"/>
</record>
<record model="ir.rule" id="rule_action2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_action"/>
</record>
<record model="ir.rule.group" id="rule_group_action_keyword">
@@ -444,11 +448,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_action_keyword1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_keyword"/>
</record>
<record model="ir.rule" id="rule_action_keyword2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_action_keyword"/>
</record>
@@ -457,11 +463,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_action_report1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_report"/>
</record>
<record model="ir.rule" id="rule_action_report2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_action_report"/>
</record>
@@ -470,11 +478,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_action_act_window1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_act_window"/>
</record>
<record model="ir.rule" id="rule_action_act_window2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_action_act_window"/>
</record>
@@ -483,11 +493,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_action_wizard1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_wizard"/>
</record>
<record model="ir.rule" id="rule_action_wizard2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_action_wizard"/>
</record>
@@ -496,11 +508,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_action_url1">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_url"/>
</record>
<record model="ir.rule" id="rule_action_url2">
<field name="domain">[('groups', '=', None)]</field>
<field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
<field name="rule_group" ref="rule_group_action_url"/>
</record>
@@ -760,7 +774,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_read" eval="False"/>
</record>
<record model="ir.rule" id="rule_sequence">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sequence"/>
</record>
@@ -770,7 +786,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_read" eval="False"/>
</record>
<record model="ir.rule" id="rule_sequence_strict">
<field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sequence_strict"/>
</record>
@@ -824,7 +842,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_view_search1">
<field name="domain">[('user', '=', user.id)]</field>
<field name="domain"
eval="[('user', '=', Eval('user', {}).get('id', -1))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_view_search"/>
</record>
@@ -850,7 +870,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_session1">
<field name="domain">[('create_uid', '=', user.id)]</field>
<field name="domain"
eval="[('create_uid', '=', Eval('user', {}).get('id', -1))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_session"/>
</record>
@@ -860,7 +882,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_session_wizard1">
<field name="domain">[('create_uid', '=', user.id)]</field>
<field name="domain"
eval="[('create_uid', '=', Eval('user', {}).get('id', -1))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_session_wizard"/>
</record>
@@ -870,7 +894,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_ui_menu_favorite1">
<field name="domain">[('user', '=', user.id)]</field>
<field name="domain"
eval="[('create_uid', '=', Eval('user', {}).get('id', -1))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_ui_menu_favorite"/>
</record>
+2 -1
View File
@@ -101,7 +101,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_user_warning1">
<field name="domain">[('user', '=', user.id)]</field>
<field name="domain"
eval="[('user', '=', Eval('user', {}).get('id', -1))]" pyson="1"/>
<field name="rule_group" ref="rule_group_user_warning"/>
</record>
+1 -19
View File
@@ -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),
+24 -13
View File
@@ -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()
-54
View File
@@ -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):
'''
+1
View File
@@ -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 }
+4 -2
View File
@@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">Collections</field>
<field name="type">ir.action.act_window</field>
<field name="res_model">webdav.collection</field>
<field name="domain">[('parent', '=', None)]</field>
<field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_collection_tree_view1">
@@ -116,7 +116,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule" id="rule_share">
<field name="domain">[('user', '=', user.id)]</field>
<field name="domain"
eval="[('user', '=', Eval('user', {}).get('id', -1))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_share"/>
</record>