[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:
parent
7c5429e0d1
commit
f737eb63d0
|
@ -6,7 +6,12 @@ Changelog
|
||||||
`trunk`
|
`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
|
- Added MONTHS attribute on fields.date and fields.datetime, holding the list
|
||||||
(month_number, month_name)
|
(month_number, month_name)
|
||||||
- Almost removed ``LocalService()``. For reports,
|
- Almost removed ``LocalService()``. For reports,
|
||||||
|
|
|
@ -19,6 +19,7 @@ OpenERP Server
|
||||||
deployment-gunicorn
|
deployment-gunicorn
|
||||||
deployment-mod-wsgi
|
deployment-mod-wsgi
|
||||||
form-view-guidelines
|
form-view-guidelines
|
||||||
|
ir_actions
|
||||||
|
|
||||||
OpenERP Command
|
OpenERP Command
|
||||||
'''''''''''''''
|
'''''''''''''''
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
|
@ -448,6 +449,27 @@ class server_object_lines(osv.osv):
|
||||||
# Actions that are run on the server side
|
# Actions that are run on the server side
|
||||||
#
|
#
|
||||||
class actions_server(osv.osv):
|
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'
|
_name = 'ir.actions.server'
|
||||||
_table = 'ir_act_server'
|
_table = 'ir_act_server'
|
||||||
_inherit = 'ir.actions.actions'
|
_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"
|
"- 'Execute Python Code': a block of python code that will be executed\n"
|
||||||
"- 'Trigger a Workflow Signal': send a signal to a workflow\n"
|
"- 'Trigger a Workflow Signal': send a signal to a workflow\n"
|
||||||
"- 'Run a Client Action': choose a client action to launch\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"
|
"- '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)"),
|
"- 'Send Email': automatically send an email (available in email_template)"),
|
||||||
'usage': fields.char('Action Usage', size=32),
|
'usage': fields.char('Action Usage', size=32),
|
||||||
'type': fields.char('Action Type', size=32, required=True),
|
'type': fields.char('Action Type', size=32, required=True),
|
||||||
|
@ -628,6 +650,9 @@ class actions_server(osv.osv):
|
||||||
(_check_write_expression,
|
(_check_write_expression,
|
||||||
'Incorrect Write Record Expression',
|
'Incorrect Write Record Expression',
|
||||||
['write_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):
|
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})
|
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):
|
def run(self, cr, uid, ids, context=None):
|
||||||
""" Run the server action, by check the condition and then calling
|
""" Run the server action. For each server action, the condition is
|
||||||
run_action_<STATE>, i.e. run_action_code, allowing easy inheritance
|
checked. Note that A void (aka False) condition is considered as always
|
||||||
of the server actions.
|
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()
|
- active_id: id of the current object (single mode)
|
||||||
calls in run()!
|
- active_model: current model that should equal the action's model
|
||||||
|
|
||||||
:param dict context: context should contain following keys:
|
The following keys are optional:
|
||||||
- active_id: current id of the object
|
|
||||||
- active_model: current model that should equal the action's model
|
|
||||||
- TDE: ?? ids: original ids
|
|
||||||
|
|
||||||
: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:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import unittest2
|
import unittest2
|
||||||
|
|
||||||
|
from openerp.osv.orm import except_orm
|
||||||
import openerp.tests.common as common
|
import openerp.tests.common as common
|
||||||
|
from openerp.tools import mute_logger
|
||||||
|
|
||||||
|
|
||||||
class TestServerActionsBase(common.TransactionCase):
|
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')])
|
cids = self.res_country.search(cr, uid, [('name', 'ilike', 'NewCountry')])
|
||||||
self.assertEqual(len(cids), 1, 'ir_actions_server: TODO')
|
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):
|
def test_60_multi(self):
|
||||||
cr, uid = self.cr, self.uid
|
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
|
# Test: action returned
|
||||||
self.assertEqual(res.get('type'), 'ir.actions.act_window', '')
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest2.main()
|
unittest2.main()
|
||||||
|
|
|
@ -5114,6 +5114,40 @@ class BaseModel(object):
|
||||||
return False
|
return False
|
||||||
return True
|
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):
|
def _get_external_ids(self, cr, uid, ids, *args, **kwargs):
|
||||||
"""Retrieve the External ID(s) of any database record.
|
"""Retrieve the External ID(s) of any database record.
|
||||||
|
|
||||||
|
|
|
@ -272,18 +272,6 @@ def reverse_enumerate(l):
|
||||||
"""
|
"""
|
||||||
return izip(xrange(len(l)-1, -1, -1), reversed(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 UpdateableStr(local):
|
||||||
""" Class that stores an updateable string (used in wizards)
|
""" Class that stores an updateable string (used in wizards)
|
||||||
|
|
Loading…
Reference in New Issue