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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
============
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,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):
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
'''
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user