Add PySON to replace python string evaluation on client side

This commit is contained in:
Cédric Krier committed 2010-01-20 10:55:19 +01:00
1 parent a964e4ac9e
commit 847fb72e90
27 files changed
+1399 -80

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Add PySON to replace python string evaluation on client side
* Add JSON-RPC
* Add groups on Sequence Types and add rules on sequence based on it.
* Ignore Fields that starts with "_"
+1
View File
@@ -8,6 +8,7 @@ Prerequisites
* lxml (http://codespeak.net/lxml/)
* relatorio 0.2.0 or later (http://relatorio.openhex.org/)
* python-dateutil (http://labix.org/python-dateutil)
* simplejson (http://undefined.org/python/#simplejson)
* Optional: psycopg 2 or later (http://www.initd.org/)
* Optional: pywebdav 0.9.3 or later (http://code.google.com/p/pywebdav/)
* Optional: pydot (http://code.google.com/p/pydot/)
+8 -7
View File
@@ -347,7 +347,7 @@ the field.
'Sale'
_name = 'sale.sale'
sale_date = fields.Date('Sale Date', required=True, states={
'readonly': "state != 'draft'",
'readonly': Not(Equal(Eval('state'), 'draft')),
})
def default_sale_date(self, cursor, user, context=None):
date_obj = self.pool.get('ir.date')
@@ -390,7 +390,8 @@ many dependencies there are.
customer = fields.Many2One('party.party', 'Customer', required=True,
states={
'readonly': "state != 'draft' or bool(outgoing_moves)",
'readonly': Or(Not(Equal(Eval('state'), 'draft')),
Bool(Eval('outgoing_moves'))),
}, on_change=['customer'])
def on_change_customer(self, cursor, user, ids, values, context=None):
@@ -415,11 +416,11 @@ will be updated with the new value.
_name = "product.template"
purchase_uom = fields.Many2One('product.uom', 'Purchase UOM', states={
'readonly': "active == False",
'invisible': "not purchasable",
'required': "purchasable",
}, domain=["('category', '=', (default_uom, 'uom.category'))"],
context="{'category': (default_uom, 'uom.category')}",
'readonly': Not(Bool(Eval('active'))),
'invisible': Not(Bool(Eval('purchasable'))),
'required': Bool(Eval('purchasable')),
}, domain=[('category', '=', (Eval('default_uom'), 'uom.category'))],
context={'category': (Eval('default_uom'), 'uom.category')},
on_change_with=['default_uom', 'purchase_uom', 'purchasable'])
def on_change_with_purchase_uom(self, cursor, user, ids, vals,
+4 -4
View File
@@ -291,7 +291,8 @@ Each tree view must start with this tag.
* ``sequence``: The name of the field that is used for sorting. So this field must be an interger and it will be updated to match the new sort when the user use the ``Drag and Drop`` between rows of the list.
* ``colors``: A string that is a list of color specification separated by ';'. The specifications have this form: ``color name:test``. The tests is evaluated on each rows and when one return True, than the color is used to highlight the row.
* ``colors``: A PySON string that will be evaluated for each record. A string containing the
name of the color will be returned.
* ``fill``: A boolean to specify if the last column must fill the remain free space in the view.
@@ -378,9 +379,8 @@ field
* ``key``: can be used to distinguish fields with the same name but
that are different with domain
* ``domain``: a string that is evaluate with the object value as
context. If the result is true the field value is added to the
graph otherwise not
* ``domain``: a PySON string which is evaluated with the object value as
context. If the result is true the field value is added to the graph.
* ``fill``: defined if the graph must be fill
+1
View File
@@ -46,6 +46,7 @@ setup(name=PACKAGE,
'relatorio >= 0.2.0',
'Genshi',
'python-dateutil',
'simplejson',
],
extras_require={
'PostgreSQL': ['psycopg2 >= 2.0'],
+95 -1
View File
@@ -2,8 +2,9 @@
#this repository contains the full copyright notices and license terms.
"Action"
from trytond.model import ModelView, ModelSQL, ModelStorage, fields
from trytond.tools import file_open, Cache
from trytond.tools import file_open, Cache, safe_eval
from trytond.backend import TableHandler
from trytond.pyson import PYSONEncoder, CONTEXT, PYSON
import base64
import os
@@ -180,6 +181,7 @@ class ActionKeyword(ModelSQL, ModelView):
('keyword', '=', keyword),
('model', '=', model + ',0'),
], context=context))
encoder = PYSONEncoder()
for action_keyword in self.browse(cursor, user, action_keyword_ids,
context=context):
try:
@@ -196,6 +198,10 @@ class ActionKeyword(ModelSQL, ModelView):
del res[-1]['report_content_data']
del res[-1]['report_content']
del res[-1]['style_content']
res[-1]['email'] = encoder.encode(res[-1]['email'])
elif action_keyword.action.type == 'ir.action.act_window':
for field in ('domain', 'context', 'search_value'):
del res[-1][field]
return res
ActionKeyword()
@@ -401,6 +407,25 @@ class ActionActWindow(ModelSQL, ModelView):
help='Use the action name as window name')
search_value = fields.Char('Search Criteria',
help='Default search criteria for the list view')
pyson_domain = fields.Function('get_pyson', type='string',
string='PySON Domain')
pyson_context = fields.Function('get_pyson', type='string',
string='PySON Context')
pyson_search_value = fields.Function('get_pyson', type='string',
string='PySON Search Criteria')
def __init__(self):
super(ActionActWindow, self).__init__()
self._constraints += [
('check_domain', 'invalid_domain'),
('check_context', 'invalid_context'),
('check_search_value', 'invalid_search_value'),
]
self._error_messages.update({
'invalid_domain': 'Invalid domain!',
'invalid_context': 'Invalid context!',
'invalid_search_value': 'Invalid search criteria!',
})
def default_type(self, cursor, user, context=None):
return 'ir.action.act_window'
@@ -423,6 +448,61 @@ class ActionActWindow(ModelSQL, ModelView):
def default_search_value(self, cursor, user, context=None):
return '{}'
def check_domain(self, cursor, user, ids):
"Check domain"
for action in self.browse(cursor, user, ids):
if action.domain:
try:
value = safe_eval(action.domain, CONTEXT)
except:
return False
if isinstance(value, PYSON):
if not value.types() == set([list]):
return False
elif not isinstance(value, list):
return False
else:
try:
fields.domain_validate(value)
except:
return False
return True
def check_context(self, cursor, user, ids):
"Check context"
for action in self.browse(cursor, user, ids):
if action.context:
try:
value = safe_eval(action.context, CONTEXT)
except:
return False
if isinstance(value, PYSON):
if not value.types() == set([dict]):
return False
elif not isinstance(value, dict):
return False
else:
try:
field.context_validate(value)
except:
return False
return True
def check_search_value(self, cursor, user, ids):
"Check search_value"
for action in self.browse(cursor, user, ids):
if action.search_value:
try:
value = safe_eval(action.search_value, CONTEXT)
except:
return False
if isinstance(value, PYSON):
if not value.types() == set([dict]):
return False
elif not isinstance(value, dict):
return False
return True
def views_get_fnc(self, cursor, user, ids, name, arg, context=None):
res = {}
for act in self.browse(cursor, user, ids, context=context):
@@ -430,6 +510,20 @@ class ActionActWindow(ModelSQL, ModelView):
for view in act.act_window_views]
return res
def get_pyson(self, cursor, user, ids, name, arg, context=None):
res = {}
encoder = PYSONEncoder()
field = name[6:]
defaults = {
'domain': '[]',
'context': '{}',
'search_value': '{}',
}
for act in self.browse(cursor, user, ids, context=context):
res[act.id] = encoder.encode(safe_eval(act[field] or
defaults.get(field, 'False'), CONTEXT))
return res
def create(self, cursor, user, vals, context=None):
later = {}
vals = vals.copy()
+2 -1
View File
@@ -3,6 +3,7 @@
"Default"
from trytond.model import ModelView, ModelSQL, fields
from trytond.tools import Cache, safe_eval
from trytond.pyson import Eval
from decimal import Decimal
import datetime
import time
@@ -16,7 +17,7 @@ class Default(ModelSQL, ModelView):
model = fields.Many2One('ir.model', 'Model', required=True,
ondelete='CASCADE')
field = fields.Many2One('ir.model.field', 'Field', required=True,
ondelete='CASCADE', domain=["('model', '=', model)"])
ondelete='CASCADE', domain=[('model', '=', Eval('model'))])
value = fields.Text('Value')
clause = fields.Text('Clause')
user = fields.Many2One('res.user', 'User', ondelete='CASCADE')
+7 -7
View File
@@ -38,17 +38,17 @@ this repository contains the full copyright notices and license terms. -->
<field name="state" readonly="1"/>
<group col="2" colspan="2" id="button">
<button string="Mark for Upgrade" name="button_upgrade"
type="object" states="{'invisible': '''state != 'installed' '''}"/>
type="object"/>
<button string="Mark for Uninstallation (beta)" name="button_uninstall"
type="object" states="{'invisible': '''state != 'installed' ''', 'readonly': '''True'''}"/>
type="object"/>
<button string="Mark for Installation" name="button_install"
type="object" states="{'invisible': '''state != 'uninstalled' '''}"/>
type="object"/>
<button string="Cancel Installation" name="button_install_cancel"
type="object" states="{'invisible': '''state != 'to install' '''}"/>
type="object"/>
<button string="Cancel Upgrade" name="button_upgrade_cancel"
type="object" states="{'invisible': '''state != 'to upgrade' '''}"/>
type="object"/>
<button string="Cancel Uninstallation" name="button_uninstall_cancel"
type="object" states="{'invisible': '''state != 'to remove' '''}"/>
type="object"/>
</group>
</form>
]]>
@@ -60,7 +60,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="arch" type="xml">
<![CDATA[
<tree string="Modules" on_write="on_write"
colors="blue:state=='to upgrade' or state=='to install';grey:state=='uninstalled'">
colors="If(In(Eval('state', ''), ['to upgrade', 'to install']), 'blue', If(Equal(Eval('state', ''), 'uninstalled'), 'grey', 'black'))">
<field name="name" select="1"/>
<field name="shortdesc" select="1"/>
<field name="author" select="2"/>
+3 -2
View File
@@ -3,6 +3,7 @@
"Rule"
from trytond.model import ModelView, ModelSQL, fields
from trytond.tools import Cache, safe_eval
from trytond.pyson import Eval, Get
import time
@@ -87,8 +88,8 @@ class Rule(ModelSQL, ModelView):
_rec_name = 'field'
_description = __doc__
field = fields.Many2One('ir.model.field', 'Field',
domain=["('model', '=', _parent_rule_group.model)"], select=1,
required=True)
domain=[('model', '=', Get(Eval('_parent_rule_group', {}), 'model'))],
select=1, required=True)
operator = fields.Selection([
('=', '='),
('<>', '<>'),
+2 -1
View File
@@ -4,6 +4,7 @@ from trytond.model import ModelView, ModelSQL, fields
from string import Template
import datetime
from trytond.tools import datetime_strftime
from trytond.pyson import In, Eval
class SequenceType(ModelSQL, ModelView):
@@ -23,7 +24,7 @@ class Sequence(ModelSQL, ModelView):
name = fields.Char('Sequence Name', required=True, translate=True)
code = fields.Selection('code_get', 'Sequence Type', required=True,
states={
'readonly': "context.get('code')",
'readonly': In('code', Eval('context', {})),
})
active = fields.Boolean('Active')
prefix = fields.Char('Prefix')
+5 -2
View File
@@ -17,6 +17,7 @@ from trytond.wizard import Wizard
from trytond import tools
from trytond.tools import file_open, reduce_ids
from trytond.backend import TableHandler
from trytond.pyson import PYSONEncoder
import os
TRANSLATION_TYPE = [
@@ -1055,8 +1056,10 @@ class TranslationUpdate(Wizard):
model_data = model_data_obj.browse(cursor, user, model_data_ids[0],
context=context)
res = act_window_obj.read(cursor, user, model_data.db_id, context=context)
res['domain'] = res['domain'][:-1] + ',' + \
str(('lang', '=', data['form']['lang'])) + res['domain'][-1:]
res['pyson_domain'] = PYSONEncoder().encode([
('module', '!=', False),
('lang', '=', data['form']['lang']),
])
return res
states = {
+34 -2
View File
@@ -7,6 +7,8 @@ import os
import logging
from lxml import etree
from trytond.backend import TableHandler
from trytond.pyson import PYSONEncoder, CONTEXT, Eval, Not, Bool, Equal
from trytond.tools import safe_eval
class View(ModelSQL, ModelView):
@@ -27,11 +29,11 @@ class View(ModelSQL, ModelView):
inherit = fields.Many2One('ir.ui.view', 'Inherited View', select=1,
ondelete='CASCADE')
field_childs = fields.Char('Children Field', states={
'invisible': "type != 'tree'",
'invisible': Not(Equal(Eval('type'), 'tree')),
})
module = fields.Char('Module', readonly=True)
domain = fields.Char('Domain', states={
'invisible': "not bool(inherit)",
'invisible': Not(Bool(Eval('inherit'))),
})
def __init__(self):
@@ -93,6 +95,36 @@ class View(ModelSQL, ModelView):
'Invalid xml view:\n%s' % (str(error_log) + '\n' + xml))
return False
root_element = tree.getroottree().getroot()
# validate pyson attributes
validates = {
'states': fields.states_validate,
'domain': fields.domain_validate,
'context': fields.context_validate,
'digits': fields.digits_validate,
'add_remove': fields.add_remove_validate,
}
def encode(element):
for attr in ('states', 'domain', 'context', 'digits',
'add_remove', 'spell', 'colors'):
if element.get(attr):
try:
value = safe_eval(element.get(attr), CONTEXT)
validates.get(attr, lambda a: True)(value)
except Exception, e:
logger = logging.getLogger('ir')
logger.error('Invalid pyson view element "%s:%s":' \
'\n%s\n%s' % \
(element.get('id') or element.get('name'),
attr, str(e), xml))
return False
for child in element:
if not encode(child):
return False
return True
if not encode(root_element):
return False
strings = self._translate_view(root_element)
view_ids = self.search(cursor, 0, [
('model', '=', view.model),
+1 -1
View File
@@ -279,7 +279,7 @@ class EvalEnvironment(dict):
return self.__getitem__(item)
except:
pass
return super(EvalEnvironment, self).get(item, default=default)
return super(EvalEnvironment, self).get(item, default)
def __nonzero__(self):
return bool(self._record)
+111 -7
View File
@@ -1,6 +1,52 @@
#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 trytond.pyson import PYSON
def domain_validate(value):
from trytond.model.modelstorage import OPERATORS
assert isinstance(value, list), 'domain must be a list'
def test_domain(dom):
for arg in dom:
if isinstance(arg, basestring):
if arg not in ('AND', 'OR'):
return False
elif isinstance(arg, tuple) or \
(isinstance(arg, list) and len(arg) > 2 and \
(arg[1] in OPERATORS) or \
(isinstance(arg[1], PYSON) and \
arg[1].types() == set([str]))):
pass
elif isinstance(arg, list):
if not test_domain(arg):
return False
return True
assert test_domain(value), 'invalid domain'
def states_validate(value):
assert isinstance(value, dict), 'states must be a dict'
for state in value:
if state == 'icon':
continue
assert isinstance(value[state], PYSON), \
'values of states must be PYSON'
assert value[state].types() == set([bool]), \
'values of states must return boolean'
def on_change_validate(value):
if value:
assert isinstance(value, list), 'on_change must be a list'
def on_change_with_validate(value):
if value:
assert isinstance(value, list), 'on_change_with must be a list'
def depends_validate(value):
assert isinstance(value, list), 'depends must be a list'
def context_validate(value):
assert isinstance(value, dict), 'context must be a dict'
class Field(object):
_type = None
@@ -18,9 +64,9 @@ class Field(object):
:param domain: A list that defines a domain constraint. See domain on
ModelStorage.search.
:param states: A dictionary. Possible keys are ``required``,
``readonly`` and ``invisible``. Values are strings of python
statements that can be evaluated with record values. This allows to
change dynamically the attributes of the field.
``readonly`` and ``invisible``. Values are pyson expressions that
will be evaluated with record values. This allows to change
dynamically the attributes of the field.
:param priority: Give the order in which set methods are called.
:param change_default: A boolean. If ``True`` the field can be used as
condition for a custom default value.
@@ -38,22 +84,80 @@ class Field(object):
:param depends: A list of field name on which this one depends.
:param order_field: A string. If set it will use the string when
ordering records on the field.
:param context: A string defining a dictionary which will be given
to open the relation fields.
:param context: A dictionary which will be given to open the relation
fields.
'''
self.string = string
self.help = help
self.required = required
self.readonly = readonly
self.__domain = None
self.domain = domain or []
assert isinstance(self.domain, list), 'domain must be a list'
self.__states = None
self.states = states or {}
self.priority = priority
self.change_default = change_default
self.translate = translate
self.select = select
self.__on_change = None
self.on_change = on_change
self.__on_change_with = None
self.on_change_with = on_change_with
self.__depends = None
self.depends = depends or []
self.order_field = order_field
self.context = context or ''
self.__context = None
self.context = context or {}
def _get_domain(self):
return self.__domain
def _set_domain(self, value):
domain_validate(value)
self.__domain = value
domain = property(_get_domain, _set_domain)
def _get_states(self):
return self.__states
def _set_states(self, value):
states_validate(value)
self.__states = value
states = property(_get_states, _set_states)
def _get_on_change(self):
return self.__on_change
def _set_on_change(self, value):
on_change_validate(value)
self.__on_change = value
on_change = property(_get_on_change, _set_on_change)
def _get_on_change_with(self):
return self.__on_change_with
def _set_on_change_with(self, value):
on_change_with_validate(value)
self.__on_change_with = value
on_change_with = property(_get_on_change_with, _set_on_change_with)
def _get_depends(self):
return self.__depends
def _set_depends(self, value):
depends_validate(value)
self.__depends = value
depends = property(_get_depends, _set_depends)
def _get_context(self):
return self.__context
def _set_context(self, value):
context_validate(value)
self.__context = value
context = property(_get_context, _set_context)
+22
View File
@@ -2,6 +2,17 @@
#this repository contains the full copyright notices and license terms.
from trytond.model.fields.field import Field
from trytond.pyson import PYSON
def digits_validate(value):
if value:
assert isinstance(value, tuple), 'digits must be a tuple'
for i in value:
assert isinstance(i, (int, long, PYSON)), \
'digits must be tuple of integers or PYSON'
if isinstance(i, PYSON):
assert i.types().issubset(set([int, long])), \
'PYSON digits must return an integer'
class Float(Field):
@@ -24,5 +35,16 @@ class Float(Field):
translate=translate, select=select, on_change=on_change,
on_change_with=on_change_with, depends=depends,
order_field=order_field, context=context)
self.__digits = None
self.digits = digits
__init__.__doc__ += Field.__init__.__doc__
def _get_digits(self):
return self.__digits
def _set_digits(self, value):
digits_validate(value)
self.__digits = value
digits = property(_get_digits, _set_digits)
+24
View File
@@ -2,6 +2,9 @@
#this repository contains the full copyright notices and license terms.
from trytond.model.fields.field import Field
from trytond.model.fields.float import digits_validate
from trytond.model.fields.one2many import add_remove_validate
from trytond.pyson import PYSON
import inspect
@@ -54,13 +57,34 @@ class Function(Field):
self._type = type
self.fnct_search = fnct_search
self.selection = selection
self.__digits = None
self.digits = digits
if relation:
self.model_name = relation
self.__add_remove = None
self.add_remove = add_remove
self.datetime_field = datetime_field
__init__.__doc__ += Field.__init__.__doc__
def _get_digits(self):
return self.__digits
def _set_digits(self, value):
digits_validate(value)
self.__digits = value
digits = property(_get_digits, _set_digits)
def _get_add_remove(self):
return self.__add_remove
def _set_add_remove(self, value):
add_remove_validate(value)
self.__add_remove = value
add_remove = property(_get_add_remove, _set_add_remove)
def search(self, cursor, user, model, name, args, context=None):
'''
Call the fnct_search.
+15
View File
@@ -4,6 +4,10 @@
from trytond.model.fields.field import Field
from itertools import chain
def add_remove_validate(value):
if value:
assert isinstance(value, list), 'add_remove must be a list'
class One2Many(Field):
'''
@@ -40,11 +44,22 @@ class One2Many(Field):
order_field=order_field, context=context)
self.model_name = model_name
self.field = field
self.__add_remove = None
self.add_remove = add_remove
self.order = order
self.datetime_field = datetime_field
__init__.__doc__ += Field.__init__.__doc__
def _get_add_remove(self):
return self.__add_remove
def _set_add_remove(self, value):
add_remove_validate(value)
self.__add_remove = value
add_remove = property(_get_add_remove, _set_add_remove)
def get(self, cursor, user, ids, model, name, values=None, context=None):
'''
Return target records ordered.
+8
View File
@@ -3,6 +3,7 @@
from trytond.model import fields
from trytond.pool import Pool
from trytond.pyson import PYSONEncoder, CONTEXT
import copy
@@ -667,6 +668,8 @@ class Model(object):
val))
translation_obj._get_sources(cursor, trans_args)
encoder = PYSONEncoder()
for field in (x for x in self._columns.keys()
if ((not fields_names) or x in fields_names)):
res[field] = {'type': self._columns[field]._type}
@@ -757,6 +760,11 @@ class Model(object):
res[field]['relation_field'] = copy.copy(
self._columns[field].field)
# convert attributes into pyson
for attr in ('states', 'domain', 'context', 'digits', 'add_remove'):
if attr in res[field]:
res[field][attr] = encoder.encode(res[field][attr])
if fields_names:
# filter out fields which aren't in the fields_names list
for i in res.keys():
+18 -21
View File
@@ -5,6 +5,7 @@ from trytond.model import fields
from trytond.model.browse import BrowseRecordList, BrowseRecord, BrowseRecordNull
from trytond.model.browse import EvalEnvironment
from trytond.tools import safe_eval
from trytond.pyson import PYSONEncoder, PYSONDecoder, PYSON
import datetime
import time
from decimal import Decimal
@@ -918,24 +919,17 @@ class ModelStorage(Model):
ctx_pref = user_obj.get_preferences(cursor, user,
context_only=True, context=context)
def eval_domain(domain, env=None):
res = []
for arg in domain:
if isinstance(arg, basestring):
if arg in ('AND', 'OR'):
res.append(arg)
else:
if not env:
return True
res.append(safe_eval(arg, env))
elif isinstance(arg, tuple):
res.append(arg)
elif isinstance(arg, list):
arg = eval_domain(arg, env=env)
if arg == True:
def is_pyson(test):
if isinstance(test, PYSON):
return True
if isinstance(test, (list, tuple)):
for i in test:
if isinstance(i, PYSON):
return True
res.append(arg)
return res
if isinstance(i, (list, tuple)):
if is_pyson(i):
return True
return False
context.update(ctx_pref)
records = self.browse(cursor, user, ids, context=context)
@@ -947,7 +941,8 @@ class ModelStorage(Model):
relation_obj = self.pool.get(field.model_name)
else:
relation_obj = field.get_target(self.pool)
if eval_domain(field.domain) == True:
if is_pyson(field.domain):
pyson_domain = PYSONEncoder().encode(field.domain)
ctx = context.copy()
ctx.update(ctx_pref)
for record in records:
@@ -957,7 +952,7 @@ class ModelStorage(Model):
env['time'] = time
env['context'] = context
env['active_id'] = record.id
domain = eval_domain(field.domain, env=env)
domain = PYSONDecoder(env).decode(pyson_domain)
relation_ids = []
if record[field_name]:
if field._type in ('many2one',):
@@ -999,9 +994,11 @@ class ModelStorage(Model):
context=context)
# validate states required
if field.states and 'required' in field.states:
if isinstance(field.states['required'], basestring):
if is_pyson(field.states['required']):
ctx = context.copy()
ctx.update(ctx_pref)
pyson_required = PYSONEncoder().encode(
field.states['required'])
for record in records:
env = EvalEnvironment(record, self)
env.update(ctx)
@@ -1009,7 +1006,7 @@ class ModelStorage(Model):
env['time'] = time
env['context'] = context
env['active_id'] = record.id
required = safe_eval(field.states['required'], env)
required = PYSONDecoder(env).decode(pyson_required)
if required and not record[field_name]:
self.raise_user_error(cursor,
'required_validation_record',
+9
View File
@@ -3,6 +3,7 @@
from trytond.model import Model
from trytond.tools import Cache, safe_eval
from trytond.modules import create_graph, get_module_list
from trytond.pyson import PYSONEncoder, CONTEXT
from lxml import etree
try:
import hashlib
@@ -374,6 +375,14 @@ 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', 'context', 'digits', 'add_remove',
'spell', 'colors'):
if element.get(attr):
element.set(attr, encoder.encode(safe_eval(element.get(attr),
CONTEXT)))
# translate view
if ('language' in context) and not result:
for attr in ('string', 'sum', 'confirm', 'help'):
+474
View File
@@ -0,0 +1,474 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
try:
import json
except ImportError:
import simplejson as json
import datetime
class PYSON(object):
def pyson(self):
raise
def types(self):
raise
@staticmethod
def eval(dct, context):
raise
class PYSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, PYSON):
return o.pyson()
return super(PYSONEncoder, self).default(o)
class PYSONDecoder(json.JSONDecoder):
def __init__(self, context=None):
self.__context = context or {}
return super(PYSONDecoder, self).__init__(object_hook=self.object_hook)
def object_hook(self, dct):
if '__class__' in dct:
klass = globals().get(dct['__class__'])
if klass and hasattr(klass, 'eval'):
return klass.eval(dct, self.__context)
return dct
class Eval(PYSON):
def __init__(self, value, default=''):
self._value = value
self._default = default
def pyson(self):
return {
'__class__': 'Eval',
'v': self._value,
'd': self._default,
}
def types(self):
if isinstance(self._default, PYSON):
return self._default.types()
else:
return set([type(self._default)])
@staticmethod
def eval(dct, context):
return context.get(dct['v'], dct['d'])
class Not(PYSON):
def __init__(self, value):
if isinstance(value, PYSON):
assert value.types() == set([bool]), 'value must be boolean'
else:
assert isinstance(value, bool), 'value must be boolean'
self._value = value
def pyson(self):
return {
'__class__': 'Not',
'v': self._value,
}
def types(self):
return set([bool])
@staticmethod
def eval(dct, context):
return not dct['v']
class Bool(PYSON):
def __init__(self, value):
self._value = value
def pyson(self):
return {
'__class__': 'Bool',
'v': self._value,
}
def types(self):
return set([bool])
@staticmethod
def eval(dct, context):
return bool(dct['v'])
class And(PYSON):
def __init__(self, *statements):
for statement in statements:
if isinstance(statement, PYSON):
assert statement.types() == set([bool]), \
'statement must be boolean'
else:
assert isinstance(statement, bool), \
'statement must be boolean'
assert len(statements) >= 2, 'must have at least 2 statements'
self._statements = statements
def pyson(self):
return {
'__class__': 'And',
's': self._statements,
}
def types(self):
return set([bool])
@staticmethod
def eval(dct, context):
return bool(reduce(lambda x, y: x and y, dct['s']))
class Or(And):
def pyson(self):
res = super(Or, self).pyson()
res['__class__'] = 'Or'
return res
@staticmethod
def eval(dct, context):
return bool(reduce(lambda x, y: x or y, dct['s']))
class Equal(PYSON):
def __init__(self, statement1, statement2):
if isinstance(statement1, PYSON):
types1 = statement1.types()
else:
types1 = set([type(statement1)])
if isinstance(statement2, PYSON):
types2 = statement2.types()
else:
types2 = set([type(statement2)])
assert types1 == types2, 'statements must have the same type'
self._statement1 = statement1
self._statement2 = statement2
def pyson(self):
return {
'__class__': 'Equal',
's1': self._statement1,
's2': self._statement2,
}
def types(self):
return set([bool])
@staticmethod
def eval(dct, context):
return dct['s1'] == dct['s2']
class Greater(PYSON):
def __init__(self, statement1, statement2, equal=False):
for i in (statement1, statement2):
if isinstance(i, PYSON):
assert i.types().issubset(set([int, long, float])), \
'statement must be an integer or a float'
else:
assert isinstance(i, (int, long, float)), \
'statement must be an integer or a float'
if isinstance(equal, PYSON):
assert equal.types() == set([bool])
else:
assert isinstance(equal, bool)
self._statement1 = statement1
self._statement2 = statement2
self._equal = equal
def pyson(self):
return {
'__class__': 'Greater',
's1': self._statement1,
's2': self._statement2,
'e': self._equal,
}
def types(self):
return set([bool])
@staticmethod
def eval(dct, context):
if dct['e']:
return dct['s1'] >= dct['s2']
else:
return dct['s1'] > dct['s2']
class Less(Greater):
def pyson(self):
res = super(Less, self).pyson()
res['__class__'] = 'Less'
return res
@staticmethod
def eval(dct, context):
if dct['e']:
return dct['s1'] <= dct['s2']
else:
return dct['s1'] < dct['s2']
class If(PYSON):
def __init__(self, condition, then_statement, else_statement=None):
if isinstance(condition, PYSON):
assert condition.types() == set([bool]), 'condition must be boolean'
else:
assert isinstance(condition, bool), 'condition must be boolean'
if isinstance(then_statement, PYSON):
then_types = then_statement.types()
else:
then_types = set([type(then_statement)])
if isinstance(else_statement, PYSON):
assert then_types == else_statement.types(), \
'then and else statements must be the same type'
else:
assert then_types == set([type(else_statement)]), \
'then and else statements must be the same type'
self._condition = condition
self._then_statement = then_statement
self._else_statement = else_statement
def pyson(self):
return {
'__class__': 'If',
'c': self._condition,
't': self._then_statement,
'e': self._else_statement,
}
def types(self):
if isinstance(self._then_statement, PYSON):
return self._then_statement.types()
else:
return set([type(self._then_statement)])
@staticmethod
def eval(dct, context):
if dct['c']:
return dct['t']
else:
return dct['e']
class Get(PYSON):
def __init__(self, obj, key, default=''):
if isinstance(obj, PYSON):
assert obj.types() == set([dict]), 'obj must be a dict'
else:
assert isinstance(obj, dict), 'obj must be a dict'
self._obj = obj
if isinstance(key, PYSON):
assert key.types() == set([str]), 'key must be a string'
else:
assert type(key) == str, 'key must be a string'
self._key = key
self._default = default
def pyson(self):
return {
'__class__': 'Get',
'v': self._obj,
'k': self._key,
'd': self._default,
}
def types(self):
if isinstance(self._default, PYSON):
return self._default.types()
else:
return set([type(self._default)])
@staticmethod
def eval(dct, context):
return dct['v'].get(dct['k'], dct['d'])
class In(PYSON):
def __init__(self, key, obj):
if isinstance(key, PYSON):
assert key.types().issubset(set([str, int, long])), \
'key must be a string or an integer or a long'
else:
assert type(key) in [str, int, long], \
'key must be a string or an integer or a long'
if isinstance(obj, PYSON):
assert obj.types().issubset(set([dict, list])), \
'obj must be a dict or a list'
if obj.types() == set([dict]):
assert type(key) == str, 'key must be a string'
else:
assert type(obj) in [dict, list]
if type(obj) == dict:
assert type(key) == str, 'key must be a string'
self._key = key
self._obj = obj
def pyson(self):
return {
'__class__': 'In',
'k': self._key,
'v': self._obj,
}
def types(self):
return set([bool])
@staticmethod
def eval(dct, context):
return dct['k'] in dct['v']
class Date(PYSON):
def __init__(self, year=None, month=None, day=None,
delta_years=0, delta_months=0, delta_days=0):
for i in (year, month, day, delta_years, delta_months, delta_days):
if isinstance(i, PYSON):
assert i.types().issubset(set([int, long, type(None)])), \
'%s must be an integer or None' % (i,)
else:
assert isinstance(i, (int, long, type(None))), \
'%s must be an integer or None' % (i,)
self._year = year
self._month = month
self._day = day
self._delta_years = delta_years
self._delta_months = delta_months
self._delta_days = delta_days
def pyson(self):
return {
'__class__': 'Date',
'y': self._year,
'M': self._month,
'd': self._day,
'dy': self._delta_years,
'dM': self._delta_months,
'dd': self._delta_days,
}
def types(self):
return set([datetime.date])
@staticmethod
def eval(dct, context):
date = datetime.date.today()
replace = {}
for i, j in (('y', 'year'), ('M', 'month'), ('d', 'day')):
if dct[i] is not None:
replace[j] = dct[i]
date = date.replace(**replace)
if dct['dy']:
year = date.year + dct['dy']
date = date.replace(year=year)
if dct['dM']:
month = date.month + dct['dM']
year = date.year + month/12
month = month%12
date = date.replace(year=year, month=month)
if dct['dd']:
date += datetime.timedelta(days=dct['dd'])
return date
class DateTime(Date):
def __init__(self, year=None, month=None, day=None,
hour=None, minute=None, second=None, microsecond=None,
delta_years=0, delta_months=0, delta_days=0,
delta_hours=0, delta_minutes=0, delta_seconds=0,
delta_microseconds=0):
super(DateTime, self).__init__(year=year, month=month, day=day,
delta_years=delta_years, delta_months=delta_months,
delta_days=delta_days)
for i in (hour, minute, second, microsecond,
delta_hours, delta_minutes, delta_seconds, delta_microseconds):
if isinstance(i, PYSON):
assert i.types() == set([int, long, type(None)]), \
'%s must be an integer or None' % (i,)
else:
assert isinstance(i, (int, long, type(None))), \
'%s must be an integer or None' % (i,)
self._hour = hour
self._minute = minute
self._second = second
self._microsecond = microsecond
self._delta_hours = delta_hours
self._delta_minutes = delta_minutes
self._delta_seconds = delta_seconds
self._delta_microseconds = delta_microseconds
def pyson(self):
res = super(DateTime, self).pyson()
res['__class__'] = 'DateTime'
res['h'] = self._hour
res['m'] = self._minute
res['s'] = self._second
res['ms'] = self._microsecond
res['dh'] = self._delta_hours
res['dm'] = self._delta_minutes
res['ds'] = self._delta_seconds
res['dms'] = self._delta_microseconds
return res
def types(self):
return set([datetime.datetime])
@staticmethod
def eval(dct, context):
date = Date.eval(dct, context)
datetime_ = datetime.datetime.combine(date,
datetime.datetime.now().time())
replace = {}
for i, j in (('h', 'hour'), ('m', 'minute'), ('s', 'second'),
('ms', 'microsecond')):
if dct[i] is not None:
replace[j] = dct[i]
datetime_ = datetime_.replace(**replace)
delta={}
for i, j in (('dh', 'hours'), ('dm', 'minutes'), ('ds', 'seconds'),
('dms', 'microseconds')):
if dct[i]:
delta[j] = dct[i]
datetime_ += datetime.timedelta(**delta)
return datetime_
CONTEXT = {
'Eval': Eval,
'Not': Not,
'Bool': Bool,
'And': And,
'Or': Or,
'Equal': Equal,
'Greater': Greater,
'Less': Less,
'If': If,
'Get': Get,
'In': In,
'Date': Date,
'DateTime': DateTime,
}
+3
View File
@@ -212,6 +212,9 @@ class Report(object):
rel_report = relatorio.reporting.Report(path, 'application/vnd.oasis.opendocument.text',
ReportFactory(), relatorio.reporting.MIMETemplateLoader())
rel_report.filters.insert(0, translator)
#convert unicode key into str
localcontext = dict(map(lambda x: (str(x[0]), x[1]),
localcontext.iteritems()))
#Test compatibility with old relatorio version <= 0.3.0
if len(inspect.getargspec(rel_report.__call__)[0]) == 2:
data = rel_report(objects, **localcontext).render().getvalue()
+12 -6
View File
@@ -408,37 +408,43 @@ this repository contains the full copyright notices and license terms. -->
expr="/form/group/button[@name=&quot;button_install&quot;]"
position="replace">
<button string="Mark for Installation" name="button_install"
type="object" states="{'invisible': '''state != 'uninstalled' ''', 'readonly': '''%(res.group_admin)d not in groups'''}"/>
type="object"
states="{'invisible': Not(Equal(Eval('state'), 'uninstalled')), 'readonly': Not(In(%(res.group_admin)d, Eval('groups', [])))}"/>
</xpath>
<xpath
expr="/form/group/button[@name=&quot;button_install_cancel&quot;]"
position="replace">
<button string="Cancel Installation" name="button_install_cancel"
type="object" states="{'invisible': '''state != 'to install' ''', 'readonly': '''%(res.group_admin)d not in groups'''}"/>
type="object"
states="{'invisible': Not(Equal(Eval('state'), 'to install')), 'readonly': Not(In(%(res.group_admin)d, Eval('groups', [])))}"/>
</xpath>
<xpath
expr="/form/group/button[@name=&quot;button_uninstall&quot;]"
position="replace">
<button string="Mark for Uninstallation (beta)" name="button_uninstall"
type="object" states="{'invisible': '''state != 'installed' ''', 'readonly': '''%(res.group_admin)d not in groups or True'''}"/>
type="object"
states="{'invisible': Not(Equal(Eval('state'), 'installed')), 'readonly': Bool(True)}"/>
</xpath>
<xpath
expr="/form/group/button[@name=&quot;button_uninstall_cancel&quot;]"
position="replace">
<button string="Cancel Uninstallation" name="button_uninstall_cancel"
type="object" states="{'invisible': '''state != 'to remove' ''', 'readonly': '''%(res.group_admin)d not in groups'''}"/>
type="object"
states="{'invisible': Not(Equal(Eval('state'), 'to remove')), 'readonly': Not(In(%(res.group_admin)d, Eval('groups', [])))}"/>
</xpath>
<xpath
expr="/form/group/button[@name=&quot;button_upgrade&quot;]"
position="replace">
<button string="Mark for Upgrade" name="button_upgrade"
type="object" states="{'invisible': '''state != 'installed' ''', 'readonly': '''%(res.group_admin)d not in groups'''}"/>
type="object"
states="{'invisible': Not(Equal(Eval('state'), 'installed')), 'readonly': Not(In(%(res.group_admin)d, Eval('groups', [])))}"/>
</xpath>
<xpath
expr="/form/group/button[@name=&quot;button_upgrade_cancel&quot;]"
position="replace">
<button string="Cancel Upgrade" name="button_upgrade_cancel"
type="object" states="{'invisible': '''state != 'to upgrade' ''', 'readonly': '''%(res.group_admin)d not in groups'''}"/>
type="object"
states="{'invisible': Not(Equal(Eval('state'), 'to upgrade')), 'readonly': Not(In(%(res.group_admin)d, Eval('groups', [])))}"/>
</xpath>
</data>
]]>
+15 -11
View File
@@ -2,6 +2,7 @@
#this repository contains the full copyright notices and license terms.
"Request"
from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import Eval, In, If, Not, Equal
import time
import datetime
@@ -18,39 +19,42 @@ _PRIORITIES = [
('2', 'High'),
]
_READONLY = If(In(Eval('state'), ['waiting', 'closed']),
True,
If(Equal(Eval('state'), 'chatting'),
Not(Equal(Eval('act_from'), Eval('_user'))),
False))
class Request(ModelSQL, ModelView):
"Request"
_name = 'res.request'
_description = __doc__
name = fields.Char('Subject', states={
'readonly': "(state in ('waiting', 'closed')) or " \
"(state == 'chatting' and act_from != _user)",
'readonly': _READONLY,
}, required=True)
active = fields.Boolean('Active')
priority = fields.Selection(_PRIORITIES, 'Priority', states={
'readonly': "(state in ('waiting', 'closed')) or " \
"(state == 'chatting' and act_from != _user)",
'readonly': _READONLY,
}, required=True, order_field='priority')
act_from = fields.Many2One('res.user', 'From', required=True,
readonly=True)
act_to = fields.Many2One('res.user', 'To', required=True,
domain=[('active', '=', True)],
states={
'readonly': "(state in ('waiting', 'closed')) or " \
"(state == 'chatting' and act_from != _user)",
'readonly': _READONLY,
})
body = fields.Text('Body', states={
'readonly': "(state in ('waiting', 'closed')) or " \
"(state == 'chatting' and act_from != _user)",
'readonly': _READONLY,
})
date_sent = fields.DateTime('Date', readonly=True)
trigger_date = fields.DateTime('Trigger Date', states={
'readonly': "(state in ('waiting', 'closed')) or " \
"(state == 'chatting' and act_from != _user)",
'readonly': _READONLY,
})
references = fields.One2Many('res.request.reference', 'request',
'References', states={
'readonly': "state == 'closed' or act_from != _user",
'readonly': If(Equal(Eval('state'), 'closed'),
True,
Not(Equal(Eval('act_from', 0), Eval('_user', 0)))),
})
number_references = fields.Function('get_number_references', type='integer',
string="Number of References", on_change_with=['references'])
+3 -3
View File
@@ -52,17 +52,17 @@ this repository contains the full copyright notices and license terms. -->
<field name="trigger_date"/>
<group col="2" colspan="2" id="buttons">
<button name="request_send"
states="{'invisible': '''state not in ('draft', 'chatting')''', 'readonly': '''act_from != _user'''}"
states="{'invisible': Not(In(Eval('state'), ['draft', 'chatting'])), 'readonly': Not(Equal(Eval('act_from'), Eval('_user')))}"
string="Send" type="object"/>
<button name="request_reply"
states="{'invisible': '''state != 'waiting' ''', 'readonly': '''act_to != _user'''}"
states="{'invisible': Not(Equal(Eval('state'), 'waiting')), 'readonly': Not(Equal(Eval('act_to'), Eval('_user')))}"
string="Reply" type="object"/>
</group>
<label name="state"/>
<field name="state"/>
<group col="3" colspan="2" id="button">
<button name="request_close"
states="{'invisible': '''state not in ('waiting', 'draft', 'chatting')'''}"
states="{'invisible': Not(In(Eval('state'), ['waiting', 'draft', 'chatting']))}"
string="Close" type="object"/>
</group>
</page>
+516
View File
@@ -14,6 +14,7 @@ import time
from decimal import Decimal
from trytond import pysocket
from trytond.tools import reduce_ids, safe_eval, datetime_strftime
from trytond import pyson
from lxml import etree
import datetime
@@ -145,6 +146,520 @@ class ToolsTestCase(unittest.TestCase):
self.assert_(datetime_strftime(datetime.date(1805, 3, 2),
'%Y-%m-%d'), '1805-03-02')
class PYSONTestCase(unittest.TestCase):
'''
Test PySON.
'''
def test0010Eval(self):
'''
Test pyson.Eval
'''
self.assert_(pyson.Eval('test').pyson() == {
'__class__': 'Eval',
'v': 'test',
'd': '',
})
self.assert_(pyson.Eval('test', 'foo').pyson() == {
'__class__': 'Eval',
'v': 'test',
'd': 'foo',
})
self.assert_(pyson.Eval('test', 'foo').types() == set([type('foo')]))
self.assert_(pyson.Eval('test', 1).types() == set([type(1)]))
eval = pyson.PYSONEncoder().encode(pyson.Eval('test', 0))
self.assert_(pyson.PYSONDecoder({'test': 1}).decode(eval) == 1)
self.assert_(pyson.PYSONDecoder().decode(eval) == 0)
def test0020Not(self):
'''
Test pyson.Not
'''
self.assert_(pyson.Not(True).pyson() == {
'__class__': 'Not',
'v': True,
})
self.assertRaises(AssertionError, pyson.Not, 'foo')
self.assert_(pyson.Not(True).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Not(True))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Not(False))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
def test0030Bool(self):
'''
Test pyson.Bool
'''
self.assert_(pyson.Bool('test').pyson() == {
'__class__': 'Bool',
'v': 'test',
})
self.assert_(pyson.Bool('test').types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Bool(True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Bool(False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Bool('test'))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Bool(''))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Bool(1))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Bool(0))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Bool(['test']))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Bool([]))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Bool({'foo': 'bar'}))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Bool({}))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
def test0040And(self):
'''
Test pyson.And
'''
self.assert_(pyson.And(True, False).pyson() == {
'__class__': 'And',
's': (True, False),
})
self.assertRaises(AssertionError, pyson.And, 'test', False)
self.assertRaises(AssertionError, pyson.And, True, 'test')
self.assertRaises(AssertionError, pyson.And, True, False, 'test')
self.assertRaises(AssertionError, pyson.And, True)
self.assertRaises(AssertionError, pyson.And)
self.assert_(pyson.And(True, False).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.And(True, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.And(True, True, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.And(True, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.And(False, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.And(False, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.And(False, False, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.And(True, False, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.And(False, True, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.And(False, False, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
def test0050Or(self):
'''
Test pyson.Or
'''
self.assert_(pyson.Or(True, False).pyson() == {
'__class__': 'Or',
's': (True, False),
})
self.assertRaises(AssertionError, pyson.Or, 'test', False)
self.assertRaises(AssertionError, pyson.Or, True, 'test')
self.assertRaises(AssertionError, pyson.Or, True, False, 'test')
self.assertRaises(AssertionError, pyson.Or, True)
self.assertRaises(AssertionError, pyson.Or)
self.assert_(pyson.Or(True, False).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Or(True, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Or(True, True, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Or(True, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Or(False, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Or(False, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Or(False, False, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Or(True, False, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Or(False, True, False))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Or(False, False, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
def test0060Equal(self):
'''
Test pyson.Equal
'''
self.assert_(pyson.Equal('test', 'test').pyson() == {
'__class__': 'Equal',
's1': 'test',
's2': 'test',
})
self.assertRaises(AssertionError, pyson.Equal, 'test', True)
self.assert_(pyson.Equal('test', 'test').types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Equal('test', 'test'))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Equal('foo', 'bar'))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
def test0070Greater(self):
'''
Test pyson.Greater
'''
self.assert_(pyson.Greater(1, 0).pyson() == {
'__class__': 'Greater',
's1': 1,
's2': 0,
'e': False,
})
self.assertRaises(AssertionError, pyson.Greater, 'test', 0)
self.assertRaises(AssertionError, pyson.Greater, 1, 'test')
self.assertRaises(AssertionError, pyson.Greater, 1, 0, 'test')
self.assert_(pyson.Greater(1, 0).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Greater(1, 0))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Greater(0, 1))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Greater(1, 0, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Greater(0, 1, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Greater(1, 1))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Greater(1, 1, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
def test0080Less(self):
'''
Test pyson.Less
'''
self.assert_(pyson.Less(0, 1).pyson() == {
'__class__': 'Less',
's1': 0,
's2': 1,
'e': False,
})
self.assertRaises(AssertionError, pyson.Less, 'test', 1)
self.assertRaises(AssertionError, pyson.Less, 0, 'test')
self.assertRaises(AssertionError, pyson.Less, 0, 1, 'test')
self.assert_(pyson.Less(0, 1).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Less(0, 1))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Less(1, 0))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Less(0, 1, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.Less(1, 0, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Less(1, 1))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.Less(1, 1, True))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
def test0090If(self):
'''
Test pyson.If
'''
self.assert_(pyson.If(True, 'foo', 'bar').pyson() == {
'__class__': 'If',
'c': True,
't': 'foo',
'e': 'bar',
})
self.assertRaises(AssertionError, pyson.If, 'test', 'foo', 'bar')
self.assertRaises(AssertionError, pyson.If, True, 'foo', False)
self.assert_(pyson.If(True, 'foo', 'bar').types() == set([type('foo')]))
self.assert_(pyson.If(True, False, True).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.If(True, 'foo', 'bar'))
self.assert_(pyson.PYSONDecoder().decode(eval) == 'foo')
eval = pyson.PYSONEncoder().encode(pyson.If(False, 'foo', 'bar'))
self.assert_(pyson.PYSONDecoder().decode(eval) == 'bar')
def test0100Get(self):
'''
Test pyson.Get
'''
self.assert_(pyson.Get({'foo': 'bar'}, 'foo', 'default').pyson() == {
'__class__': 'Get',
'v': {'foo': 'bar'},
'k': 'foo',
'd': 'default',
})
self.assertRaises(AssertionError, pyson.Get, 'test', 'foo', 'default')
self.assertRaises(AssertionError, pyson.Get, {}, 1, 'default')
self.assert_(pyson.Get({}, 'foo', 'default').types() == set([str]))
self.assert_(pyson.Get({}, 'foo', True).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.Get(
{'foo': 'bar'}, 'foo', 'default'))
self.assert_(pyson.PYSONDecoder().decode(eval) == 'bar')
eval = pyson.PYSONEncoder().encode(pyson.Get(
{'foo': 'bar'}, 'test', 'default'))
self.assert_(pyson.PYSONDecoder().decode(eval) == 'default')
eval = pyson.PYSONEncoder().encode(pyson.Get(
{}, 'foo', 'default'))
self.assert_(pyson.PYSONDecoder().decode(eval) == 'default')
def test0110In(self):
'''
Test pyson.In
'''
self.assert_(pyson.In('foo', {'foo': 'bar'}).pyson() == {
'__class__': 'In',
'k': 'foo',
'v': {'foo': 'bar'},
})
self.assertRaises(AssertionError, pyson.In, object(), {})
self.assertRaises(AssertionError, pyson.In, 'test', 'foo')
self.assert_(pyson.In('foo', {}).types() == set([bool]))
eval = pyson.PYSONEncoder().encode(pyson.In('foo', {'foo': 'bar'}))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.In('1', {1: 'bar'}))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.In('test', {'foo': 'bar'}))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.In('1', {2: 'bar'}))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.In('test', {}))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.In('foo', ['foo']))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.In(1, [1]))
self.assert_(pyson.PYSONDecoder().decode(eval) == True)
eval = pyson.PYSONEncoder().encode(pyson.In('test', ['foo']))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.In(1, [2]))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
eval = pyson.PYSONEncoder().encode(pyson.In('test', []))
self.assert_(pyson.PYSONDecoder().decode(eval) == False)
def test0120Date(self):
'''
Test pyson.Date
'''
self.assert_(pyson.Date(2010, 1, 12, -1, 12, -7).pyson() == {
'__class__': 'Date',
'y': 2010,
'M': 1,
'd': 12,
'dy': -1,
'dM': 12,
'dd': -7
})
self.assertRaises(AssertionError, pyson.Date, 'test', 1, 12, -1, 12, -7)
self.assertRaises(AssertionError, pyson.Date, 2010, 'test', 12, -1, 12, -7)
self.assertRaises(AssertionError, pyson.Date, 2010, 1, 'test', -1, 12, -7)
self.assertRaises(AssertionError, pyson.Date, 2010, 1, 12, 'test', 12, -7)
self.assertRaises(AssertionError, pyson.Date, 2010, 1, 12, -1, 'test', -7)
self.assertRaises(AssertionError, pyson.Date, 2010, 1, 12, -1, 12, 'test')
self.assert_(pyson.Date(2010, 1, 12, -1, 12, -7).types()
== set([datetime.date]))
eval = pyson.PYSONEncoder().encode(pyson.Date())
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.date.today())
eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.date(2010, 1, 12))
eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12, -1))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.date(2009, 1, 12))
eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12, 0, 12))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.date(2011, 1, 12))
eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12, 0, 0, -7))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.date(2010, 1, 5))
def test0130DateTime(self):
'''
Test pyson.DateTime
'''
self.assert_(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
-1, 12, -7, 2, 15, 30, 1).pyson() == {
'__class__': 'DateTime',
'y': 2010,
'M': 1,
'd': 12,
'h': 10,
'm': 30,
's': 20,
'ms': 0,
'dy': -1,
'dM': 12,
'dd': -7,
'dh': 2,
'dm': 15,
'ds': 30,
'dms': 1,
})
self.assertRaises(AssertionError, pyson.DateTime, 'test', 1, 12, 10,
30, 20, 0, -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 'test', 12, 10,
30, 20, 0, -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 'test', 10,
30, 20, 0, -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 'test',
30, 20, 0, -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10,
'test', 20, 0, -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
'test', 0, -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 'test', -1, 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, 'test', 12, -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, -1, 'test', -7, 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, -1, 12, 'test', 2, 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, -1, 12, -7, 'test', 15, 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, -1, 12, -7, 2, 'test', 30, 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, -1, 12, -7, 2, 15, 'test', 1)
self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
20, 0, -1, 12, -7, 2, 15, 30, 'test')
self.assert_(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
-1, 12, -7, 2, 15, 30, 1).types() == set([datetime.datetime]))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2010, 1, 12, 10, 30, 20, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, -1))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2009, 1, 12, 10, 30, 20, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, 0, 12))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2011, 1, 12, 10, 30, 20, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, 0, 0, -7))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2010, 1, 5, 10, 30, 20, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, 0, 0, 0, 12))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2010, 1, 12, 22, 30, 20, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, 0, 0, 0, 0, -30))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2010, 1, 12, 10, 0, 20, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, 0, 0, 0, 0, 0, 30))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2010, 1, 12, 10, 30, 50, 0))
eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
10, 30, 20, 0, 0, 0, 0, 0, 0, 0, 200))
self.assert_(pyson.PYSONDecoder().decode(eval)
== datetime.datetime(2010, 1, 12, 10, 30, 20, 200))
def test0900Composite(self):
'''
Test Composite
'''
eval = pyson.PYSONEncoder().encode(['id', pyson.If(pyson.Not(
pyson.In('company', pyson.Eval('context', {}))), '=', '!='),
pyson.Get(pyson.Eval('context', {}), 'company', 0)])
self.assert_(pyson.PYSONDecoder({'context': {'company': 1}}
).decode(eval) == ['id', '!=', 1])
self.assert_(pyson.PYSONDecoder({'context': {}}
).decode(eval) == ['id', '=', 0])
class FieldsTestCase(unittest.TestCase):
'''
@@ -2488,6 +3003,7 @@ def suite():
if __name__ == '__main__':
suite = suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(ToolsTestCase))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(PYSONTestCase))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(FieldsTestCase))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(ModelSingletonTestCase))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(ModelViewTestCase))
+5 -4
View File
@@ -6,6 +6,7 @@ from trytond.model import ModelView, ModelSQL, fields
from trytond.report import Report
from trytond.tools import exec_command_pipe
from trytond.backend import TableHandler
from trytond.pyson import Eval, Equal, Not
import expr
import base64
@@ -67,14 +68,14 @@ class WorkflowActivity(ModelSQL, ModelView):
('stopall', 'Stop All'),
], 'Kind', required=True)
action = fields.Text('Action', states={
'readonly': "kind == 'dummy'",
'required': "kind == 'function'",
'readonly': Equal(Eval('kind'), 'dummy'),
'required': Equal(Eval('kind'), 'function'),
})
flow_start = fields.Boolean('Flow Start')
flow_stop = fields.Boolean('Flow Stop')
subflow = fields.Many2One('workflow', 'Subflow', states={
'readonly': "kind != 'subflow'",
'required': "kind == 'subflow'",
'readonly': Not(Equal(Eval('kind'), 'subflow')),
'required': Equal(Eval('kind'), 'subflow'),
})
signal_send = fields.Char('Signal (subflow.*)')
out_transitions = fields.One2Many('workflow.transition', 'act_from',