diff --git a/CHANGELOG b/CHANGELOG index 6fafb29105..6581fb64a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 "_" diff --git a/INSTALL b/INSTALL index 4a6eb2bf9f..3fd31829fe 100644 --- a/INSTALL +++ b/INSTALL @@ -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/) diff --git a/doc/models.rst b/doc/models.rst index ed8eacbb23..a0cbeb2760 100644 --- a/doc/models.rst +++ b/doc/models.rst @@ -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, diff --git a/doc/views.rst b/doc/views.rst index bf2289fc09..522d9db578 100644 --- a/doc/views.rst +++ b/doc/views.rst @@ -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 diff --git a/setup.py b/setup.py index 688d5d3ace..565bc110e4 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ setup(name=PACKAGE, 'relatorio >= 0.2.0', 'Genshi', 'python-dateutil', + 'simplejson', ], extras_require={ 'PostgreSQL': ['psycopg2 >= 2.0'], diff --git a/trytond/ir/action.py b/trytond/ir/action.py index e0cb97e6fd..07df5f0898 100644 --- a/trytond/ir/action.py +++ b/trytond/ir/action.py @@ -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() diff --git a/trytond/ir/default.py b/trytond/ir/default.py index 683695b26f..820eacda61 100644 --- a/trytond/ir/default.py +++ b/trytond/ir/default.py @@ -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') diff --git a/trytond/ir/module/module.xml b/trytond/ir/module/module.xml index 7d6adfff3b..d62f47b637 100644 --- a/trytond/ir/module/module.xml +++ b/trytond/ir/module/module.xml @@ -38,17 +38,17 @@ this repository contains the full copyright notices and license terms. -->