Add PySON to replace python string evaluation on client side
This commit is contained in:
1 parent
a964e4ac9e
commit
847fb72e90
27 files changed
+1399
-80
No files matched your search
@@ -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 "_"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ setup(name=PACKAGE,
|
||||
'relatorio >= 0.2.0',
|
||||
'Genshi',
|
||||
'python-dateutil',
|
||||
'simplejson',
|
||||
],
|
||||
extras_require={
|
||||
'PostgreSQL': ['psycopg2 >= 2.0'],
|
||||
|
||||
+95
-1
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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([
|
||||
('=', '='),
|
||||
('<>', '<>'),
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
@@ -408,37 +408,43 @@ this repository contains the full copyright notices and license terms. -->
|
||||
expr="/form/group/button[@name="button_install"]"
|
||||
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="button_install_cancel"]"
|
||||
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="button_uninstall"]"
|
||||
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="button_uninstall_cancel"]"
|
||||
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="button_upgrade"]"
|
||||
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="button_upgrade_cancel"]"
|
||||
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
@@ -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'])
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in new issue
Block a user