Browse Source

[IMP] add security file

pull/417/head
David Beal 8 years ago
parent
commit
cf27d3b835
  1. 1
      .travis.yml
  2. 31
      help_popup/README.rst
  3. 2
      help_popup/__init__.py
  4. 5
      help_popup/__openerp__.py
  5. 79
      help_popup/demo/help.xml
  6. 2
      help_popup/models/__init__.py
  7. 109
      help_popup/models/action_window.py
  8. 40
      help_popup/models/models.py
  9. 4
      help_popup/report/all.xml
  10. 2
      help_popup/report/help.xml
  11. 12
      help_popup/security/group.xml
  12. 3
      help_popup/security/ir.model.access.csv
  13. BIN
      help_popup/static/description/contributor.png
  14. BIN
      help_popup/static/description/doc.png
  15. BIN
      help_popup/static/description/nodoc.png
  16. BIN
      help_popup/static/description/popup.png
  17. BIN
      help_popup/static/description/sales.png
  18. BIN
      help_popup/static/description/sales_doc.png
  19. 3
      help_popup/static/src/js/popup_help.js
  20. 1
      help_popup/tests/__init__.py
  21. 44
      help_popup/tests/test_help.py
  22. 11
      help_popup/views/action_view.xml

1
.travis.yml

@ -32,6 +32,7 @@ install:
- git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly - travis_install_nightly
- pip install bs4
script: script:
- travis_run_tests - travis_run_tests

31
help_popup/README.rst

@ -10,36 +10,31 @@ It brings to end users inline documentation.
Some parts of the documentation can be modified by anyone (with proper rights). Some parts of the documentation can be modified by anyone (with proper rights).
Configuration
=============
* Go to any view and click on the `?` near the title view.
* Edit the html field to add content
* You can provide documentation with this module by appending data
in the field advanced_help (relative to action) or advanced_help_model
if your help must be associated to model instead of action
Usage Usage
===== =====
Click on ? button
Read the documentation below
.. image:: help_popup/static/description/popup.png .. image:: help_popup/static/description/popup.png
:alt: License: Help Popup
:alt: Help Popup
Here is the documentation for the whole sales menu with a summary
.. image:: help_popup/static/description/sales_doc.png
:alt: Documentaion for main menu
Alternative Alternative
----------- -----------
If you have website module installed, it could be an option If you have website module installed, it could be an option
to install help_online instead of this module.
to install help_online instead of this module. Just compare them.
Help Online is more advanced (allow the end user to add help)
but depends on an other module.
Help popup is more like an embedded help that use power users for end users.
Roadmap / issue
===============
* For v9 move to knowledge repository
* Allow to plug an online translator to reduce the translation effort to the popup.
Bug Tracker Bug Tracker

2
help_popup/__init__.py

@ -1 +1 @@
from . import model
from . import models

5
help_popup/__openerp__.py

@ -16,10 +16,15 @@
'report/report.xml', 'report/report.xml',
'report/help.xml', 'report/help.xml',
'report/all.xml', 'report/all.xml',
'security/group.xml',
'security/ir.model.access.csv',
], ],
'demo': [ 'demo': [
'demo/help.xml', 'demo/help.xml',
], ],
'external_dependencies': {
'python': ['bs4'],
},
'qweb': [ 'qweb': [
'static/src/xml/popup_help.xml', 'static/src/xml/popup_help.xml',
], ],

79
help_popup/demo/help.xml

@ -7,22 +7,9 @@
<field name="enduser_help"><![CDATA[ <field name="enduser_help"><![CDATA[
<b>Hi Odooer,</b> <b>Hi Odooer,</b>
<br/>
<br/>
<p>
I'm the field 'enduser_help' in the Customer action model
</p>
<p>
I'm displayed in a Qweb html report
</p>
<p>
Don't hesitate to customized me with your own words and syntax
</p>
<p>You can write any html tag. Here is an image with img tag</p>
<img src="http://www.akretion.com/sites/50443990c3c67e1bf3000004/theme/images/logo.png"/>
<p/>
Here is a documentation tool. You can customize these lines.
Check lines below to learn more about this feature.
]]></field> ]]></field>
@ -32,24 +19,48 @@ Don't hesitate to customized me with your own words and syntax
<field name="advanced_help"><![CDATA[ <field name="advanced_help"><![CDATA[
<b>Hi Odoo community,</b> <b>Hi Odoo community,</b>
<br/>
<br/>
<p>
I'm the field 'advanced_help' in the customer action also displayed in Qweb report.
</p>
<p>
I wrote these words to explain the main purpose:
<blockquote>
Odoo community companies can insert their documenation here with data in their modules. <br><br>
Then, any end users can see this documentation in one click.
<br>
End users can also display and print documentation about an entire menu.
</blockquote>
</p>
<p>
</p>
<h4>Main functionalities:</h4>
<ul>
<li>Documentation is contextual by model or action: the right help to the good place.</li>
<li>Modules contributors can embeds their documentation in their modules: documentation access in 1 click by model or action.</li>
<li>End users can also produce some html documentation to complete missing parts.</li>
<li>Documentation can also been edited for a complete base menu: Sales, Purchase, etc.</li>
<li>Documentation can be printed with <kbd>Ctrl + P</kbd> shortcut.</li>
</ul>
<p/>
<h4>How to use it:</h4>
<ul>
<li>When you see on any user interface part <img src="/help_popup/static/description/nodoc.png"/> click on '?'.</li>
<li>Click on edit and fill 'End Users help' field.</li>
<li>When you see <img src="/help_popup/static/description/doc.png"/> click on '?' button to access directly to the documentation.</li>
<li>With the 2 buttons on the right, can either edit help or call the documentation of the whole main menus <img src="/help_popup/static/description/sales.png"/></li>
</ul>
<p>
<h4>How to embedds documentation in your module:</h4>
<ul>
<li>You can insert html with standard Odoo xml data.</li>
<li>Data are inserted with 'ir.actions.act_window' model</li>
<li>If your data concerns a model, then fill the field 'advanced_help_model' of your action.</li>
<li>If your data is specific to 1 action window (i.e. only customers, not suppliers) then fill the field 'avanced_help' with your data.</li>
<li>Example of inserted data
<br/><code>
&lt;record id="base.action_partner_form" model="ir.actions.act_window"&gt;
<br/> &nbsp; &nbsp;
&lt;field name="advanced_help"&gt;&lt;![CDATA[
<p>put your html data here</p>
<br/> &nbsp; &nbsp;
]]&gt;&lt;/field&gt;
<br/>
&lt;/record&gt;</code>
</li>
<li>You can do of the same way with 'advanced_help_model' field.</li>
<li><b>Several modules can modify a fraction of the same field: remove of data at uninstall is taken account.</b></li>
</ul>
<p/>
<img src="/help_popup/static/description/contributor.png"/>
<p/>
]]></field> ]]></field>
</record> </record>
@ -68,7 +79,7 @@ Use these multicategories to classify your partners
<record id="base.action_res_bank_form" <record id="base.action_res_bank_form"
model="ir.actions.act_window"> model="ir.actions.act_window">
<field name="enduser_help"><![CDATA[
<field name="enduser_help_model"><![CDATA[
Hi all,<br> Hi all,<br>
<br><br> <br><br>
Who does not want to rob a bank? Who does not want to rob a bank?

2
help_popup/models/__init__.py

@ -0,0 +1,2 @@
from . import models
from . import action_window

109
help_popup/model.py → help_popup/models/action_window.py

@ -2,36 +2,18 @@
# © 2015 David BEAL @ Akretion <david.beal@akretion.com> # © 2015 David BEAL @ Akretion <david.beal@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from BeautifulSoup import BeautifulSoup as BSHTML
import logging
import inspect import inspect
from openerp import _, api, models, fields from openerp import _, api, models, fields
from openerp.exceptions import Warning as UserError
_logger = logging.getLogger(__name__)
class ErpHelp(models.AbstractModel):
_name = 'erp.help'
enduser_help = fields.Html(
string="End User Help",
help="Use this field to add custom content for documentation purpose\n"
"mainly by power users ")
advanced_help = fields.Text(
string="Advanced Help", groups='base.group_no_one',
help="Use this field to add custom content for documentation purpose\n"
"mainly by developers or consultants")
# @api.multi
# def write(self, vals):
# if not self._context.get('install_mode') and 'advanced_help' in vals:
# raise UserError(_("Advanced help field must only be updated "
# "by install mode (not with the user interface"))
# return super(ErpHelp, self).write(vals)
class IrModel(models.Model):
_inherit = ['ir.model', 'erp.help']
_name = 'ir.model'
try:
from bs4 import BeautifulSoup as BSHTML
except ImportError:
_logger.debug(
'Beautifulsoup required for help_popup module is not installed')
class IrActionsActwindow(models.Model): class IrActionsActwindow(models.Model):
@ -40,17 +22,18 @@ class IrActionsActwindow(models.Model):
_rpt_menu = False _rpt_menu = False
enduser_help_model = fields.Html( enduser_help_model = fields.Html(
string='Enduser Help from Model', store="True",
compute='_compute_model_help', inverse='_inverse_model_help',
string='Enduser Help from Model', related='model_id.enduser_help',
help="") help="")
advanced_help_model = fields.Text( advanced_help_model = fields.Text(
string='Advanced Help from model', store="True",
compute='_compute_model_help', inverse='_inverse_model_help',
string='Advanced Help from model', related='model_id.advanced_help',
help="") help="")
action_help = fields.Boolean(string="Display Action Help") action_help = fields.Boolean(string="Display Action Help")
help_has_content = fields.Boolean( help_has_content = fields.Boolean(
string="Content in help", compute='_compute_contains_help', string="Content in help", compute='_compute_contains_help',
help="One of the help has content")
help="One of the help field has content")
model_id = fields.Many2one(
string='Model', comodel_name='ir.model', store=True,
compute='_compute_model_id')
@api.one @api.one
@api.depends('enduser_help', 'advanced_help', @api.depends('enduser_help', 'advanced_help',
@ -62,30 +45,29 @@ class IrActionsActwindow(models.Model):
else: else:
self.help_has_content = False self.help_has_content = False
@api.multi
def _compute_model_help(self):
for rec in self:
model = rec.env['ir.model'].search([('model', '=', rec.res_model)])
rec.enduser_help_model = model.enduser_help
rec.advanced_help_model = model.advanced_help
def _inverse_model_help(self):
for rec in self:
model = rec.env['ir.model'].search([('model', '=', rec.res_model)])
model.enduser_help = rec.enduser_help_model
model.advanced_help = rec.advanced_help_model
@api.one
@api.depends('res_model')
def _compute_model_id(self):
if self.res_model:
model = self.env['ir.model'].search(
[('model', '=', self.res_model)])
if model:
self.model_id = model.id
@api.multi @api.multi
def write(self, vals): def write(self, vals):
if self._context.get('install_mode'): if self._context.get('install_mode'):
module_name = self.module_being_update_or_insert()
for field in ['advanced_help']:
if field in vals:
module_name = self.module_being_processing()
for field in ['advanced_help', 'advanced_help_model']:
if module_name and field in vals:
self._update_help_field(vals, field, module_name) self._update_help_field(vals, field, module_name)
return super(IrActionsActwindow, self).write(vals) return super(IrActionsActwindow, self).write(vals)
@api.multi @api.multi
def _update_help_field(self, vals, field, module_name): def _update_help_field(self, vals, field, module_name):
""" update partially the content of the field according to
which inserted information inside
"""
new_val_field = u'<help_%s>%s</help_%s>' % ( new_val_field = u'<help_%s>%s</help_%s>' % (
module_name, vals[field] or '', module_name) module_name, vals[field] or '', module_name)
original_val_field = vals[field] original_val_field = vals[field]
@ -105,17 +87,21 @@ class IrActionsActwindow(models.Model):
self[field], new_val_field) self[field], new_val_field)
@api.model @api.model
def module_being_update_or_insert(self):
def module_being_processing(self):
for elm in inspect.stack(): for elm in inspect.stack():
arg_values = inspect.getargvalues(elm[0]) arg_values = inspect.getargvalues(elm[0])
if 'locals' in arg_values.__dict__: if 'locals' in arg_values.__dict__:
if arg_values.__dict__['locals'].get('module'):
module = arg_values.__dict__['locals'].get('module')
if module not in self.env.registry:
return arg_values.__dict__['locals'].get('module')
module_name = arg_values.__dict__['locals'].get('module')
if module_name and \
module_name not in self.env.registry._init_modules:
return module_name
# We don't know stack evolution in future versions, alert required
_logger.warning("'module_being_processing()' hasn't found "
"module name in the stack. Please check this method.")
return False
@api.multi @api.multi
def open_help_popup(self):
def button_open_help_popup(self):
""" Open in a new tab instead of in popup""" """ Open in a new tab instead of in popup"""
self.ensure_one() self.ensure_one()
return { return {
@ -127,7 +113,7 @@ class IrActionsActwindow(models.Model):
@api.model @api.model
def get_help_actions(self): def get_help_actions(self):
""" called by the template"""
""" called by qweb template"""
self._rpt_menu = self.get_main_menu() self._rpt_menu = self.get_main_menu()
menu_names = self.get_menu_names(self._rpt_menu) menu_names = self.get_menu_names(self._rpt_menu)
actions = self.search([ actions = self.search([
@ -150,7 +136,7 @@ class IrActionsActwindow(models.Model):
('value', '=', 'ir.actions.act_window,%s' % self.id), ('value', '=', 'ir.actions.act_window,%s' % self.id),
]) ])
if ir_vals: if ir_vals:
# we only keep the first menu beacause we have no info on menu_id
# we only keep the first menu because we have no info on menu_id
self._rpt_menu = self.env['ir.ui.menu'].browse(ir_vals[0].res_id) self._rpt_menu = self.env['ir.ui.menu'].browse(ir_vals[0].res_id)
while self._rpt_menu.parent_id: while self._rpt_menu.parent_id:
self._rpt_menu = self._rpt_menu.parent_id self._rpt_menu = self._rpt_menu.parent_id
@ -171,7 +157,22 @@ class IrActionsActwindow(models.Model):
return {int(x.value[22:]): map_menu[x.res_id] for x in ir_vals} return {int(x.value[22:]): map_menu[x.res_id] for x in ir_vals}
def _anchorize(self, string): def _anchorize(self, string):
""" called by template """
""" called by qweb template """
for char in ["'", '"', ' ']: for char in ["'", '"', ' ']:
string = string.replace(char, '-') string = string.replace(char, '-')
return string return string
@api.one
def remove_obsolete_help(self, module_name):
""" We need to remove some partial content of the file """
for field in ['advanced_help', 'advanced_help_model']:
tag = getattr(
BSHTML(self[field]), 'help_%s' % module_name)
if tag:
old_content = ''.join(
[unicode(x) for x in tag.contents if x])
if old_content:
to_replace = '<help_%s>%s</help_%s>' % (
module_name, old_content, module_name)
value = self[field].replace(to_replace, '')
self.write({field: value})

40
help_popup/models/models.py

@ -0,0 +1,40 @@
# coding: utf-8
# © 2015 David BEAL @ Akretion <david.beal@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, models, fields
class ErpHelp(models.AbstractModel):
_name = 'erp.help'
enduser_help = fields.Html(
string="End User Help",
help="Use this field to add custom content for documentation purpose\n"
"mainly by power users ")
advanced_help = fields.Text(
string="Advanced Help", groups='base.group_no_one',
help="Use this field to add custom content for documentation purpose\n"
"mainly by developers or consultants")
class IrModel(models.Model):
_inherit = ['ir.model', 'erp.help']
_name = 'ir.model'
class IrModuleModule(models.Model):
_inherit = 'ir.module.module'
@api.multi
def module_uninstall(self):
if self.name != 'help_popup':
domain = ['|',
('advanced_help', 'like', '%' + self.name + '%'),
('advanced_help_model', 'like', '%' + self.name + '%')]
actions = self.env['ir.actions.act_window'].search(domain)
for record in actions:
record.with_context(
help_uninstall=True).remove_obsolete_help(self.name)
return super(IrModuleModule, self).module_uninstall()

4
help_popup/report/all.xml

@ -23,8 +23,8 @@
</ul> </ul>
<t t-foreach="actions" t-as="act"> <t t-foreach="actions" t-as="act">
<h2 t-attf-id="-{{ o._anchorize(act.name) }}" t-raw='act.name'/> <h2 t-attf-id="-{{ o._anchorize(act.name) }}" t-raw='act.name'/>
<div class="bg-warning" t-if="act.enduser_help" t-raw="act.enduser_help"/>
<div class="bg-warning" t-if="act.enduser_help_model" t-raw="act.enduser_help_model"/>
<div class="bg-warning" style="padding:5px;" t-if="act.enduser_help" t-raw="act.enduser_help"/>
<div class="bg-warning" style="padding:5px;" t-if="act.enduser_help_model" t-raw="act.enduser_help_model"/>
<hr width="70%"/> <hr width="70%"/>
<div t-if="act.advanced_help" t-raw="act.advanced_help"/> <div t-if="act.advanced_help" t-raw="act.advanced_help"/>
<div t-if="act.advanced_help_model" t-raw="act.advanced_help_model"/> <div t-if="act.advanced_help_model" t-raw="act.advanced_help_model"/>

2
help_popup/report/help.xml

@ -21,7 +21,7 @@
<br/> <br/>
</div> </div>
<br/> <br/>
<div class="bg-warning" t-if="o.enduser_help_model or o.enduser_help">
<div class="bg-warning" style="padding:5px;" t-if="o.enduser_help_model or o.enduser_help">
<div class="oe_text_center label label-info">Internal</div> <div class="oe_text_center label label-info">Internal</div>
<div t-if="o.enduser_help_model" t-raw="o.enduser_help_model"/> <div t-if="o.enduser_help_model" t-raw="o.enduser_help_model"/>
<div t-if="o.enduser_help" t-raw="o.enduser_help"/> <div t-if="o.enduser_help" t-raw="o.enduser_help"/>

12
help_popup/security/group.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="res.groups" id="group_help">
<field name="name">Inline Help</field>
</record>
</data>
</openerp>

3
help_popup/security/ir.model.access.csv

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ir_model_group_help,ir_model_group_help,model_ir_model,group_help,1,1,0,0
access_ir_actions_act_window_group_help,ir_actions_act_window_group_help,model_ir_actions_act_window,group_help,1,1,0,0

BIN
help_popup/static/description/contributor.png

After

Width: 210  |  Height: 133  |  Size: 6.9 KiB

BIN
help_popup/static/description/doc.png

After

Width: 156  |  Height: 42  |  Size: 3.3 KiB

BIN
help_popup/static/description/nodoc.png

After

Width: 168  |  Height: 46  |  Size: 3.5 KiB

BIN
help_popup/static/description/popup.png

Before

Width: 874  |  Height: 793  |  Size: 121 KiB

After

Width: 830  |  Height: 772  |  Size: 248 KiB

BIN
help_popup/static/description/sales.png

After

Width: 72  |  Height: 25  |  Size: 1.3 KiB

BIN
help_popup/static/description/sales_doc.png

After

Width: 805  |  Height: 687  |  Size: 53 KiB

3
help_popup/static/src/js/popup_help.js

@ -1,6 +1,7 @@
openerp.help_popup = function(instance, local) { openerp.help_popup = function(instance, local) {
var _t = instance.web._t; var _t = instance.web._t;
instance.web.ViewManager.include({ instance.web.ViewManager.include({
do_create_view: function(view_type) { do_create_view: function(view_type) {
@ -14,7 +15,7 @@ openerp.help_popup = function(instance, local) {
return true; return true;
} }
$elem.data('click-init', true); $elem.data('click-init', true);
console.log('me ' + self)
console.log('blabla ')
if (self.action.id == undefined || self.action.help_has_content == true) { if (self.action.id == undefined || self.action.help_has_content == true) {
self.$el.find('span.update_help').hide() self.$el.find('span.update_help').hide()
} }

1
help_popup/tests/__init__.py

@ -0,0 +1 @@
from . import test_help

44
help_popup/tests/test_help.py

@ -0,0 +1,44 @@
# coding: utf-8
# © 2016 David BEAL @ Akretion <david.beal@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# import logging
from openerp.tests import common
class TestHelpPopup(common.TransactionCase):
def test_qweb_help_report(self):
partner_act = self.env.ref('base.action_partner_form')
return partner_act.button_open_help_popup()
# def test_update_partially_field(self):
# partner_act = self.env.ref('base.action_partner_form')
# import pdb; pdb.set_trace()
# partner_act.with_context(install_mode=True).write(
# {'advanced_help': 'new_content'})
# partner_act.with_context(install_mode=True).write(
# {'advanced_help': 'toto'})
# # import pdb; pdb.set_trace()
# # vals = {'advanced_help': 'toto'}
# # partner_act._update_help_field(vals, 'advanced_help', 'help_popup')
# self.assertEqual(
# partner_act.advanced_help,
# u'\nnew_content<help_base>toto</help_base>',
# "Partially updating of 'ir.model'.advanced_help is broken")
# # partner_act.remove_obsolete_help('base')
def test_advanced_user_model_help(self):
""" test if advanced_help_model field in 'ir.actions.act_window'
propagate changes in 'ir.model' advanced_help field
"""
partner_act = self.env.ref('base.action_partner_form')
partner_act.with_context(install_mode=True).write(
{'advanced_help_model': 'new_content'})
partner_mod = self.env['ir.model'].search(
[('model', '=', 'res.partner')])
self.assertEqual(
partner_mod.advanced_help, 'new_content',
"advanced_help and avanced_help_model fields are not equals")

11
help_popup/views/action_view.xml

@ -10,6 +10,7 @@
<field name="help" position="after"> <field name="help" position="after">
<field name="enduser_help"/> <field name="enduser_help"/>
<field name="advanced_help"/> <field name="advanced_help"/>
<field name="help_has_content"/>
</field> </field>
</field> </field>
</record> </record>
@ -22,14 +23,14 @@
<group name="main" col="4"> <group name="main" col="4">
<field name="name" attrs="{'readonly': True}"/> <field name="name" attrs="{'readonly': True}"/>
<field name="res_model" attrs="{'readonly': True}"/> <field name="res_model" attrs="{'readonly': True}"/>
<field name="help_has_content"/>
<field name="action_help"/> <field name="action_help"/>
<button name="open_help_popup" type="object" string="Help"
class="oe_highlight"/><span/>
<!-- <field name="help_has_content"/> -->
<button name="button_open_help_popup" type="object"
string="Help" class="oe_highlight"/><span/>
<separator string="End users help" colspan="4"/> <separator string="End users help" colspan="4"/>
<field name="enduser_help_model" colspan="4" nolabel="1"/> <field name="enduser_help_model" colspan="4" nolabel="1"/>
<div attrs="{'invisible': [('action_help', '=', False)]}" <div attrs="{'invisible': [('action_help', '=', False)]}"
class="alert alert-warning"
class="alert alert-warning oe_edit_only"
colspan="4">Help field below is only used by action (i.e. action for 'customer' is different than action for 'supplier' but share the same model)</div> colspan="4">Help field below is only used by action (i.e. action for 'customer' is different than action for 'supplier' but share the same model)</div>
<field name="enduser_help" colspan="4" nolabel="1" <field name="enduser_help" colspan="4" nolabel="1"
attrs="{'invisible': [('action_help', '=', False)]}"/> attrs="{'invisible': [('action_help', '=', False)]}"/>
@ -66,7 +67,7 @@
<field name="res_model">ir.actions.act_window</field> <field name="res_model">ir.actions.act_window</field>
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="target">current</field> <field name="target">current</field>
<!-- <field name="context">{'search_default_customer':1, 'restrict_kind': ['individu', 'incorpo'], 'default_kind':'incorpo'}</field> -->
<field name="context">{'search_help_has_content':1}</field>
</record> </record>
<record model="ir.actions.act_window.view" id="act_help_popup_formr_view_form"> <record model="ir.actions.act_window.view" id="act_help_popup_formr_view_form">

Loading…
Cancel
Save