[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
This commit is contained in:
Thibault Delavallée 2013-07-25 12:49:14 +02:00
parent 7c5429e0d1
commit f737eb63d0
6 changed files with 89 additions and 26 deletions

View File

@ -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,

View File

@ -19,6 +19,7 @@ OpenERP Server
deployment-gunicorn
deployment-mod-wsgi
form-view-guidelines
ir_actions
OpenERP Command
'''''''''''''''

View File

@ -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.
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 = {}

View File

@ -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()

View File

@ -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.

View File

@ -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)