summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibault Delavallée <tde@openerp.com>2013-07-25 12:49:14 +0200
committerThibault Delavallée <tde@openerp.com>2013-07-25 12:49:14 +0200
commitf737eb63d06e728ae754bcbed8efe0e41994bd0b (patch)
tree53ad674e59ce16c417360f5da2991673f8f176bf
parent7c5429e0d189977012f9ec5c763c84010b9391b7 (diff)
[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
-rw-r--r--doc/changelog.rst7
-rw-r--r--doc/index.rst1
-rw-r--r--openerp/addons/base/ir/ir_actions.py53
-rw-r--r--openerp/addons/base/tests/test_ir_actions.py8
-rw-r--r--openerp/osv/orm.py34
-rw-r--r--openerp/tools/misc.py12
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_<STATE>, 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_<STATE> method is called. This
+ allows easy inheritance of the server actions.
+
+ :param dict context: context should contain following keys
- A void (aka False) condition is considered as always valid.
+ - active_id: id of the current object (single mode)
+ - active_model: current model that should equal the action's model
- Note coming from previous implementation: FIXME: refactor all the eval()
- calls in run()!
+ The following keys are optional:
- :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
+ - active_ids: ids of the current records (mass mode). If active_ids
+ and active_id are present, active_ids is given precedence.
- :return: False: finished correctly or action_id: action to lanch
+ :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)