[MERGE] client_actions and ir.todo rework by xmo
bzr revid: al@openerp.com-20110804115501-prpmo6lwrm6z989o
This commit is contained in:
commit
934e7e8f8e
|
@ -54,7 +54,6 @@
|
|||
'module/wizard/base_import_language_view.xml',
|
||||
'module/wizard/base_module_upgrade_view.xml',
|
||||
'module/wizard/base_module_configuration_view.xml',
|
||||
'module/wizard/base_module_shortcut_data.xml',
|
||||
'module/wizard/base_export_language_view.xml',
|
||||
'module/wizard/base_update_translations_view.xml',
|
||||
'res/res_request_view.xml',
|
||||
|
|
|
@ -106,6 +106,11 @@ CREATE TABLE ir_act_server (
|
|||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_act_client (
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
|
||||
CREATE TABLE ir_ui_view (
|
||||
id serial NOT NULL,
|
||||
|
|
|
@ -306,9 +306,8 @@
|
|||
<record id="config_wizard_simple_view" model="ir.actions.todo">
|
||||
<field name="action_id" ref="action_config_simple_view_form"/>
|
||||
<field name="category_id" ref="category_administration_config"/>
|
||||
<field name="type">special</field>
|
||||
<field name="type">automatic</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="state">skip</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -1889,7 +1889,7 @@
|
|||
<field name="type"/>
|
||||
<field name="state" readonly="1"/>
|
||||
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
|
||||
<button name="action_open" states="cancel,skip,done"
|
||||
<button name="action_open" states="done"
|
||||
string="Todo" type="object" help="Set as Todo"
|
||||
icon="gtk-convert"/>
|
||||
</tree>
|
||||
|
@ -1913,7 +1913,7 @@
|
|||
<group colspan="4" col="4">
|
||||
<field name="state" colspan="2" readonly="1"/>
|
||||
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
|
||||
<button name="action_open" states="cancel,skip,done"
|
||||
<button name="action_open" states="done"
|
||||
string="Set as Todo" type="object"
|
||||
icon="gtk-convert"/>
|
||||
</group>
|
||||
|
|
|
@ -18,20 +18,21 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields,osv
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
import tools
|
||||
import time
|
||||
from tools.config import config
|
||||
from tools.translate import _
|
||||
import netsvc
|
||||
import logging
|
||||
import re
|
||||
import ast
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import tools
|
||||
from xml import dom
|
||||
|
||||
import netsvc
|
||||
from osv import fields,osv
|
||||
from report.report_sxw import report_sxw, report_rml
|
||||
from tools.config import config
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools.translate import _
|
||||
|
||||
class actions(osv.osv):
|
||||
_name = 'ir.actions.actions'
|
||||
|
@ -162,13 +163,13 @@ class act_window(osv.osv):
|
|||
|
||||
def _invalid_model_msg(self, cr, uid, ids, context=None):
|
||||
return _('Invalid model name in the action definition.')
|
||||
|
||||
|
||||
_constraints = [
|
||||
(_check_model, _invalid_model_msg, ['res_model','src_model'])
|
||||
]
|
||||
|
||||
def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
|
||||
"""Returns an ordered list of the specific view modes that should be
|
||||
"""Returns an ordered list of the specific view modes that should be
|
||||
enabled when displaying the result of this action, along with the
|
||||
ID of the specific view to use for each mode, if any were required.
|
||||
|
||||
|
@ -208,7 +209,7 @@ class act_window(osv.osv):
|
|||
if act.search_view_id:
|
||||
search_view_id = act.search_view_id.id
|
||||
else:
|
||||
res_view = self.pool.get('ir.ui.view').search(cr, uid,
|
||||
res_view = self.pool.get('ir.ui.view').search(cr, uid,
|
||||
[('model','=',act.res_model),('type','=','search'),
|
||||
('inherit_id','=',False)], context=context)
|
||||
if res_view:
|
||||
|
@ -808,7 +809,7 @@ class ir_actions_todo_category(osv.osv):
|
|||
_name = 'ir.actions.todo.category'
|
||||
_description = "Configuration Wizard Category"
|
||||
_columns = {
|
||||
'name':fields.char('Name', size=64, translate=True, required=True),
|
||||
'name':fields.char('Name', size=64, translate=True, required=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'wizards_ids': fields.one2many('ir.actions.todo', 'category_id', 'Configuration Wizards'),
|
||||
}
|
||||
|
@ -816,10 +817,9 @@ ir_actions_todo_category()
|
|||
|
||||
# This model use to register action services.
|
||||
TODO_STATES = [('open', 'To Do'),
|
||||
('done', 'Done'),
|
||||
('skip','Skipped'),
|
||||
('cancel','Cancelled')]
|
||||
|
||||
('done', 'Done')]
|
||||
TODO_TYPES = [('manual', 'Launch Manually'),
|
||||
('automatic', 'Launch Automatically')]
|
||||
class ir_actions_todo(osv.osv):
|
||||
"""
|
||||
Configuration Wizards
|
||||
|
@ -833,10 +833,9 @@ class ir_actions_todo(osv.osv):
|
|||
'sequence': fields.integer('Sequence'),
|
||||
'state': fields.selection(TODO_STATES, string='State', required=True),
|
||||
'name': fields.char('Name', size=64),
|
||||
'type': fields.selection([('special','Special'),('normal','Normal'),('normal_recurring','Normal Recurring')], 'Type', required=True,
|
||||
help="""Special: the wizard is run whenever the system is reconfigured.
|
||||
Normal: the wizard is visible in the configuration panel until it is done.
|
||||
Normal Recurring: the wizard is visible in the configuration panel regardless of its state."""),
|
||||
'type': fields.selection(TODO_TYPES, 'Type', required=True,
|
||||
help="""Manual: Launched manually.
|
||||
Automatic: Runs whenever the system is reconfigured."""),
|
||||
'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
|
||||
'note': fields.text('Text', translate=True),
|
||||
'category_id': fields.many2one('ir.actions.todo.category','Category'),
|
||||
|
@ -844,33 +843,108 @@ Normal Recurring: the wizard is visible in the configuration panel regardless of
|
|||
_defaults={
|
||||
'state': 'open',
|
||||
'sequence': 10,
|
||||
'type': 'special',
|
||||
'type': 'manual',
|
||||
}
|
||||
_order="sequence,name,id"
|
||||
|
||||
def action_launch(self, cr, uid, ids, context=None):
|
||||
""" Launch Action of Wizard"""
|
||||
if context is None:
|
||||
context = {}
|
||||
wizard_id = ids and ids[0] or False
|
||||
wizard = self.browse(cr, uid, wizard_id, context=context)
|
||||
if wizard.type == 'automatic':
|
||||
wizard.write({'state': 'done'})
|
||||
|
||||
res = self.pool.get('ir.actions.act_window').read(cr, uid, wizard.action_id.id, [], context=context)
|
||||
res.update({'nodestroy': True})
|
||||
res['nodestroy'] = True
|
||||
|
||||
# Open a specific record when res_id is provided in the context
|
||||
if res.get('context'):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
ctx = eval(res['context'], {'user': user})
|
||||
if ctx.get('res_id'):
|
||||
res.update({'res_id': ctx.pop('res_id')})
|
||||
res.update({'context': ctx})
|
||||
res.update(
|
||||
res_id=ctx.pop('res_id'),
|
||||
context=ctx)
|
||||
return res
|
||||
|
||||
def action_open(self, cr, uid, ids, context=None):
|
||||
""" Sets configuration wizard in TODO state"""
|
||||
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
|
||||
|
||||
def progress(self, cr, uid, context=None):
|
||||
""" Returns a dict with 3 keys {todo, done, total}.
|
||||
|
||||
These keys all map to integers and provide the number of todos
|
||||
marked as open, the total number of todos and the number of
|
||||
todos not open (which is basically a shortcut to total-todo)
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
user_groups = set(map(
|
||||
lambda x: x.id,
|
||||
self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
|
||||
def groups_match(todo):
|
||||
""" Checks if the todo's groups match those of the current user
|
||||
"""
|
||||
return not todo.groups_id \
|
||||
or bool(user_groups.intersection((
|
||||
group.id for group in todo.groups_id)))
|
||||
|
||||
done = filter(
|
||||
groups_match,
|
||||
self.browse(cr, uid,
|
||||
self.search(cr, uid, ['&', ('state', '!=', 'open'), ('type', '=', 'manual')], context=context),
|
||||
context=context))
|
||||
|
||||
total = filter(
|
||||
groups_match,
|
||||
self.browse(cr, uid,
|
||||
self.search(cr, uid, [('type', '=', 'manual')], context=context),
|
||||
context=context))
|
||||
|
||||
return {
|
||||
'done': len(done),
|
||||
'total': len(total),
|
||||
'todo': len(total) - len(done)
|
||||
}
|
||||
|
||||
ir_actions_todo()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
class act_client(osv.osv):
|
||||
_name = 'ir.actions.client'
|
||||
_inherit = 'ir.actions.actions'
|
||||
_table = 'ir_act_client'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_order = 'name'
|
||||
|
||||
def _get_params(self, cr, uid, ids, field_name, arg, context):
|
||||
return dict([
|
||||
((record.id, ast.literal_eval(record.params_store))
|
||||
if record.params_store else (record.id, False))
|
||||
for record in self.browse(cr, uid, ids, context=context)
|
||||
])
|
||||
|
||||
def _set_params(self, cr, uid, ids, field_name, field_value, arg, context):
|
||||
assert isinstance(field_value, dict), "params can only be dictionaries"
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
record.write({field_name: repr(field_value)})
|
||||
|
||||
_columns = {
|
||||
'tag': fields.char('Client action tag', size=64, required=True,
|
||||
help="An arbitrary string, interpreted by the client"
|
||||
" according to its own needs and wishes. There "
|
||||
"is no central tag repository across clients."),
|
||||
'params': fields.function(_get_params, fnct_inv=_set_params,
|
||||
type='binary', method=True,
|
||||
string="Supplementary arguments",
|
||||
help="Arguments sent to the client along with"
|
||||
"the view tag"),
|
||||
'params_store': fields.binary("Params storage", readonly=True)
|
||||
}
|
||||
_defaults = {
|
||||
'type': 'ir.actions.client',
|
||||
|
||||
}
|
||||
act_client()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -273,6 +273,7 @@ class ir_ui_menu(osv.osv):
|
|||
('ir.actions.wizard', 'ir.actions.wizard'),
|
||||
('ir.actions.url', 'ir.actions.url'),
|
||||
('ir.actions.server', 'ir.actions.server'),
|
||||
('ir.actions.client', 'ir.actions.client'),
|
||||
]),
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ class base_module_configuration(osv.osv_memory):
|
|||
_name = "base.module.configuration"
|
||||
|
||||
def start(self, cr, uid, ids, context=None):
|
||||
todo_ids = self.pool.get('ir.actions.todo').search(cr, uid, ['|', '|', ('type','=','normal_recurring'), ('state', '=', 'open'), '&', ('state', '=', 'skip'), ('type', '=', 'special')])
|
||||
todo_ids = self.pool.get('ir.actions.todo').search(cr, uid,
|
||||
['|', ('type','=','recurring'), ('state', '=', 'open')])
|
||||
if not todo_ids:
|
||||
# When there is no wizard todo it will display message
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
|
|
|
@ -16,28 +16,5 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_start_configurator" model="ir.actions.server">
|
||||
<field name="name">Start Configuration</field>
|
||||
<field name="model_id" ref="model_base_module_configuration"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">action = obj.start()</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Add More Features"
|
||||
action="action_start_configurator"
|
||||
id="menu_view_base_module_configuration" parent="base.menu_config"
|
||||
type="server" icon="STOCK_EXECUTE" sequence="100" />
|
||||
|
||||
<record model="ir.values" id="action_todo_config">
|
||||
<field name="model_id" ref="model_ir_actions_todo" />
|
||||
<field name="object" eval="1" />
|
||||
<field name="name">Start Configuration</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('action_start_configurator'))" />
|
||||
<field name="key">action</field>
|
||||
<field name="model">ir.actions.todo</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="ir_ui_view_sc_configuration" model="ir.ui.view_sc">
|
||||
<field name="name">Add More Features</field>
|
||||
<field name="resource">ir.ui.menu</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="res_id" ref="menu_view_base_module_configuration"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
|
@ -37,16 +37,14 @@ class res_config_configurable(osv.osv_memory):
|
|||
'''
|
||||
_name = 'res.config'
|
||||
_inherit = 'ir.wizard.screen'
|
||||
logger = netsvc.Logger()
|
||||
__logger = logging.getLogger(_name)
|
||||
|
||||
def get_current_progress(self, cr, uid, context=None):
|
||||
'''Return a description the current progress of configuration:
|
||||
a tuple of (non_open_todos:int, total_todos: int)
|
||||
'''
|
||||
todo_pool = self.pool.get('ir.actions.todo')
|
||||
return (todo_pool.search_count(cr, uid, [('state','<>','open')], context),
|
||||
todo_pool.search_count(cr, uid, [], context))
|
||||
return itemgetter('done', 'total')(
|
||||
self.pool.get('ir.actions.todo').progress(cr, uid, context=context))
|
||||
|
||||
def _progress(self, cr, uid, context=None):
|
||||
closed, total = self.get_current_progress(cr, uid, context=context)
|
||||
|
@ -63,51 +61,35 @@ class res_config_configurable(osv.osv_memory):
|
|||
)
|
||||
|
||||
def _next_action(self, cr, uid, context=None):
|
||||
todos = self.pool.get('ir.actions.todo')
|
||||
self.__logger.info('getting next %s', todos)
|
||||
# Don't forget to change the domain in search view, if this condition is changed
|
||||
active_todos = todos.search(cr, uid, [('state','=','open')],
|
||||
limit=1)
|
||||
if active_todos:
|
||||
todo_obj = todos.browse(cr, uid, active_todos[0], context=None)
|
||||
todo_groups = map(lambda x:x.id, todo_obj.groups_id)
|
||||
dont_skip_todo = True
|
||||
if todo_groups:
|
||||
cr.execute("select 1 from res_groups_users_rel where uid=%s and gid IN %s",(uid, tuple(todo_groups),))
|
||||
dont_skip_todo = bool(cr.fetchone())
|
||||
if dont_skip_todo:
|
||||
res = todos.browse(cr, uid, active_todos[0], context=None)
|
||||
# A wizard opening directly a form instead of calling
|
||||
# next_action will remain in the todo state so we set it to
|
||||
# done ourselves.
|
||||
if res.action_id.target == 'current':
|
||||
res.write({'state': 'done'})
|
||||
return res
|
||||
else:
|
||||
todos.write(cr, uid, active_todos[0], {'state':'skip'}, context=None)
|
||||
return self._next_action(cr, uid)
|
||||
return None
|
||||
Todos = self.pool['ir.actions.todo']
|
||||
self.__logger.info('getting next %s', Todos)
|
||||
|
||||
def _set_previous_todo(self, cr, uid, state, context=None):
|
||||
""" lookup the previous (which is still the next at this point)
|
||||
ir.actions.todo, set it to whatever state was provided.
|
||||
"""
|
||||
# this is ultra brittle, but apart from storing the todo id
|
||||
# into the res.config view, I'm not sure how to get the
|
||||
# "previous" todo
|
||||
previous_todo = self._next_action(cr, uid, context=context)
|
||||
if not previous_todo:
|
||||
self.__logger.warn(_("Couldn't find previous ir.actions.todo"))
|
||||
return
|
||||
previous_todo.write({'state':state})
|
||||
active_todos = Todos.browse(cr, uid,
|
||||
Todos.search(cr, uid, ['&', ('type', '=', 'automatic'), ('state','=','open')]),
|
||||
context=context)
|
||||
|
||||
user_groups = set(map(
|
||||
lambda g: g.id,
|
||||
self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
|
||||
|
||||
valid_todos_for_user = [
|
||||
todo for todo in active_todos
|
||||
if not todo.groups_id or bool(user_groups.intersection((
|
||||
group.id for group in todo.groups_id)))
|
||||
]
|
||||
|
||||
if valid_todos_for_user:
|
||||
return valid_todos_for_user[0]
|
||||
|
||||
return None
|
||||
|
||||
def _next(self, cr, uid, context=None):
|
||||
self.__logger.info('getting next operation')
|
||||
next = self._next_action(cr, uid)
|
||||
next = self._next_action(cr, uid, context=context)
|
||||
self.__logger.info('next action is %s', next)
|
||||
if next:
|
||||
res = next.action_launch(context=context)
|
||||
res.update({'nodestroy': False})
|
||||
res['nodestroy'] = False
|
||||
return res
|
||||
self.__logger.info('all configuration actions have been executed')
|
||||
|
||||
|
@ -116,11 +98,6 @@ class res_config_configurable(osv.osv_memory):
|
|||
return self.pool.get(current_user_menu.type).read(cr, uid, current_user_menu.id)
|
||||
|
||||
def start(self, cr, uid, ids, context=None):
|
||||
todo_pool = self.pool.get('ir.actions.todo')
|
||||
ids2 = todo_pool.search(cr, uid, [], context=context)
|
||||
for todo in todo_pool.browse(cr, uid, ids2, context=context):
|
||||
if (todo.type=='normal_recurring'):
|
||||
todo.write({'state':'open'})
|
||||
return self.next(cr, uid, ids, context)
|
||||
|
||||
def next(self, cr, uid, ids, context=None):
|
||||
|
@ -162,8 +139,7 @@ class res_config_configurable(osv.osv_memory):
|
|||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
self._set_previous_todo(cr, uid, state='done', context=context)
|
||||
next = self.execute(cr, uid, ids, context=None)
|
||||
next = self.execute(cr, uid, ids, context=context)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
|
||||
|
@ -175,8 +151,7 @@ class res_config_configurable(osv.osv_memory):
|
|||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
self._set_previous_todo(cr, uid, state='skip', context=context)
|
||||
next = self.cancel(cr, uid, ids, context=None)
|
||||
next = self.cancel(cr, uid, ids, context=context)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
|
||||
|
@ -191,8 +166,7 @@ class res_config_configurable(osv.osv_memory):
|
|||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
self._set_previous_todo(cr, uid, state='cancel', context=context)
|
||||
next = self.cancel(cr, uid, ids, context=None)
|
||||
next = self.cancel(cr, uid, ids, context=context)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
|
||||
|
@ -296,10 +270,24 @@ class res_config_installer(osv.osv_memory):
|
|||
|
||||
_install_if = {}
|
||||
|
||||
def already_installed(self, cr, uid, context=None):
|
||||
""" For each module, check if it's already installed and if it
|
||||
is return its name
|
||||
|
||||
:returns: a list of the already installed modules in this
|
||||
installer
|
||||
:rtype: [str]
|
||||
"""
|
||||
return map(attrgetter('name'),
|
||||
self._already_installed(cr, uid, context=context))
|
||||
|
||||
def _already_installed(self, cr, uid, context=None):
|
||||
""" For each module (boolean fields in a res.config.installer),
|
||||
check if it's already installed (either 'to install', 'to upgrade' or 'installed')
|
||||
and if it is, check it by default
|
||||
check if it's already installed (either 'to install', 'to upgrade'
|
||||
or 'installed') and if it is return the module's browse_record
|
||||
|
||||
:returns: a list of all installed modules in this installer
|
||||
:rtype: [browse_record]
|
||||
"""
|
||||
modules = self.pool.get('ir.module.module')
|
||||
|
||||
|
@ -351,8 +339,8 @@ class res_config_installer(osv.osv_memory):
|
|||
if base.issuperset(requirements)
|
||||
for module in consequences)
|
||||
|
||||
return (base | hooks_results | additionals) - set(
|
||||
map(attrgetter('name'), self._already_installed(cr, uid, context)))
|
||||
return (base | hooks_results | additionals).difference(
|
||||
self.already_installed(cr, uid, context))
|
||||
|
||||
def default_get(self, cr, uid, fields_list, context=None):
|
||||
''' If an addon is already installed, check it by default
|
||||
|
@ -362,8 +350,7 @@ class res_config_installer(osv.osv_memory):
|
|||
|
||||
return dict(defaults,
|
||||
**dict.fromkeys(
|
||||
map(attrgetter('name'),
|
||||
self._already_installed(cr, uid, context=context)),
|
||||
self.already_installed(cr, uid, context=context),
|
||||
True))
|
||||
|
||||
def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
|
||||
|
@ -374,12 +361,12 @@ class res_config_installer(osv.osv_memory):
|
|||
fields = super(res_config_installer, self).fields_get(
|
||||
cr, uid, fields, context, write_access)
|
||||
|
||||
for module in self._already_installed(cr, uid, context=context):
|
||||
if module.name not in fields:
|
||||
for name in self.already_installed(cr, uid, context=context):
|
||||
if name not in fields:
|
||||
continue
|
||||
fields[module.name].update(
|
||||
fields[name].update(
|
||||
readonly=True,
|
||||
help= ustr(fields[module.name].get('help', '')) +
|
||||
help= ustr(fields[name].get('help', '')) +
|
||||
_('\n\nThis addon is already installed on your system'))
|
||||
return fields
|
||||
|
||||
|
|
Loading…
Reference in New Issue