From f737eb63d06e728ae754bcbed8efe0e41994bd0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 25 Jul 2013 12:49:14 +0200 Subject: [PATCH] [IMP] ir_actions_server: added doc + comments + recursion check orm: added _check_m2my_recursion, to check loops in many2many recursive fields tools: removed sms_send bzr revid: tde@openerp.com-20130725104914-dutxfon3odp8z167 --- doc/changelog.rst | 7 ++- doc/index.rst | 1 + openerp/addons/base/ir/ir_actions.py | 53 +++++++++++++++----- openerp/addons/base/tests/test_ir_actions.py | 8 +++ openerp/osv/orm.py | 34 +++++++++++++ openerp/tools/misc.py | 12 ----- 6 files changed, 89 insertions(+), 26 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 9a84e5dfd00..02196aa5e28 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,7 +6,12 @@ Changelog `trunk` ------- -- Cleaned and slightly refactored ``ir.actions.server`` +- Cleaned and slightly refactored ``ir.actions.server``. The ``loop``, ``sms`` + and ``dummy`` server actions have been removed; ``object_create`` and + ``object_copy`` have been merged into ``object_create``; ``other`` is now ``multi`` + and raises in case of loops. See :ref:`ir-actions-server` for more details. +- Removed ``sms_send`` method. +- Added checking of recursions in many2many loops using ``_check_m2m_recursion``. - Added MONTHS attribute on fields.date and fields.datetime, holding the list (month_number, month_name) - Almost removed ``LocalService()``. For reports, diff --git a/doc/index.rst b/doc/index.rst index e641de1da67..a5af574d09e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,6 +19,7 @@ OpenERP Server deployment-gunicorn deployment-mod-wsgi form-view-guidelines + ir_actions OpenERP Command ''''''''''''''' diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 7bad1e7f0dd..e05ba533fa3 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -19,6 +19,7 @@ # ############################################################################## +from functools import partial import logging import operator import os @@ -448,6 +449,27 @@ class server_object_lines(osv.osv): # Actions that are run on the server side # class actions_server(osv.osv): + """ Server actions model. Server action work on a base model and offer various + type of actions that can be executed automatically, for example using base + action rules, of manually, by adding the action in the 'More' contextual + menu. + + Since OpenERP 8.0 a button 'Create Menu Action' button is available on the + action form view. It creates an entry in the More menu of the base model. + This allows to create server actions and run them in mass mode easily through + the interface. + + The available actions are : + + - 'Execute Python Code': a block of python code that will be executed + - 'Trigger a Workflow Signal': send a signal to a workflow + - 'Run a Client Action': choose a client action to launch + - 'Create or Copy a new Record': create a new record with new values, or + copy an existing record in your database + - 'Write on a Record': update the values of a record + - 'Execute several actions': define an action that triggers several other + server actions + """ _name = 'ir.actions.server' _table = 'ir_act_server' _inherit = 'ir.actions.actions' @@ -487,9 +509,9 @@ class actions_server(osv.osv): "- 'Execute Python Code': a block of python code that will be executed\n" "- 'Trigger a Workflow Signal': send a signal to a workflow\n" "- 'Run a Client Action': choose a client action to launch\n" - "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" + "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" "- 'Write on a Record': update the values of a record\n" - "- 'Execute several actions': define an action that triggers several other sever actions\n" + "- 'Execute several actions': define an action that triggers several other server actions\n" "- 'Send Email': automatically send an email (available in email_template)"), 'usage': fields.char('Action Usage', size=32), 'type': fields.char('Action Type', size=32, required=True), @@ -628,6 +650,9 @@ class actions_server(osv.osv): (_check_write_expression, 'Incorrect Write Record Expression', ['write_expression']), + (partial(osv.Model._check_m2m_recursion, field_name='child_ids'), + 'Recursion found in child server actions', + ['child_ids']), ] def on_change_model_id(self, cr, uid, ids, model_id, wkf_model_id, crud_model_id, context=None): @@ -895,21 +920,23 @@ class actions_server(osv.osv): self.pool[action.model_id.model].write(cr, uid, [context.get('active_id')], {action.link_field_id.name: res_id}) def run(self, cr, uid, ids, context=None): - """ Run the server action, by check the condition and then calling - run_action_, i.e. run_action_code, allowing easy inheritance - of the server actions. + """ Run the server action. For each server action, the condition is + checked. Note that A void (aka False) condition is considered as always + valid. If it is verified, the run_action_ method is called. This + allows easy inheritance of the server actions. - A void (aka False) condition is considered as always valid. + :param dict context: context should contain following keys - Note coming from previous implementation: FIXME: refactor all the eval() - calls in run()! + - active_id: id of the current object (single mode) + - active_model: current model that should equal the action's model - :param dict context: context should contain following keys: - - active_id: current id of the object - - active_model: current model that should equal the action's model - - TDE: ?? ids: original ids + The following keys are optional: - :return: False: finished correctly or action_id: action to lanch + - active_ids: ids of the current records (mass mode). If active_ids + and active_id are present, active_ids is given precedence. + + :return: an action_id to be executed, or False is finished correctly without + return action """ if context is None: context = {} diff --git a/openerp/addons/base/tests/test_ir_actions.py b/openerp/addons/base/tests/test_ir_actions.py index c0e1de13845..04f67617ad6 100644 --- a/openerp/addons/base/tests/test_ir_actions.py +++ b/openerp/addons/base/tests/test_ir_actions.py @@ -1,6 +1,8 @@ import unittest2 +from openerp.osv.orm import except_orm import openerp.tests.common as common +from openerp.tools import mute_logger class TestServerActionsBase(common.TransactionCase): @@ -354,6 +356,7 @@ self.pool["res.partner"].create(cr, uid, {"name": partner_name}, context=context cids = self.res_country.search(cr, uid, [('name', 'ilike', 'NewCountry')]) self.assertEqual(len(cids), 1, 'ir_actions_server: TODO') + @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm') def test_60_multi(self): cr, uid = self.cr, self.uid @@ -385,6 +388,11 @@ self.pool["res.partner"].create(cr, uid, {"name": partner_name}, context=context # Test: action returned self.assertEqual(res.get('type'), 'ir.actions.act_window', '') + # Test loops + self.assertRaises(except_orm, self.ir_actions_server.write, cr, uid, [self.act_id], { + 'child_ids': [(6, 0, [self.act_id])] + }) + if __name__ == '__main__': unittest2.main() diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index eefe013cc42..163abcb006d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -5114,6 +5114,40 @@ class BaseModel(object): return False return True + def _check_m2m_recursion(self, cr, uid, ids, field_name): + """ + Verifies that there is no loop in a hierarchical structure of records, + by following the parent relationship using the **parent** field until a loop + is detected or until a top-level record is found. + + :param cr: database cursor + :param uid: current user id + :param ids: list of ids of records to check + :param field_name: field to check + :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected. + """ + + field = self._all_columns.get(field_name) + field = field.column if field else None + if not field or field._type != 'many2many' or field._obj != self._name: + # field must be a many2many on itself + raise ValueError('invalid field_name: %r' % (field_name,)) + + query = 'SELECT distinct "%s" FROM "%s" WHERE "%s" IN %%s' % (field._id2, field._rel, field._id1) + ids_parent = ids[:] + while ids_parent: + ids_parent2 = [] + for i in range(0, len(ids_parent), cr.IN_MAX): + j = i + cr.IN_MAX + sub_ids_parent = ids_parent[i:j] + cr.execute(query, (tuple(sub_ids_parent),)) + ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall()))) + ids_parent = ids_parent2 + for i in ids_parent: + if i in ids: + return False + return True + def _get_external_ids(self, cr, uid, ids, *args, **kwargs): """Retrieve the External ID(s) of any database record. diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 7a54d6b9b6e..4f2d8177c0f 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -272,18 +272,6 @@ def reverse_enumerate(l): """ return izip(xrange(len(l)-1, -1, -1), reversed(l)) -#---------------------------------------------------------- -# SMS -#---------------------------------------------------------- -# text must be latin-1 encoded -def sms_send(user, password, api_id, text, to): - import urllib - url = "http://api.urlsms.com/SendSMS.aspx" - #url = "http://196.7.150.220/http/sendmsg" - params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to}) - urllib.urlopen(url+"?"+params) - # FIXME: Use the logger if there is an error - return True class UpdateableStr(local): """ Class that stores an updateable string (used in wizards)